From dd2f94d3d2954c58d6426cc41d49a690dd8737ff Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 25 Feb 2016 01:43:05 +0300 Subject: [PATCH 01/95] Fixed torrenttelik playlist --- plugins/torrenttelik_plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/torrenttelik_plugin.py b/plugins/torrenttelik_plugin.py index 163040d..443cf16 100644 --- a/plugins/torrenttelik_plugin.py +++ b/plugins/torrenttelik_plugin.py @@ -26,7 +26,8 @@ def downloadPlaylist(self, url): try: req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) Torrenttelik.playlist = urllib2.urlopen(req, timeout=10).read() - Torrenttelik.playlist = Torrenttelik.playlist.split('\xef\xbb\xbf')[1] # garbage at the beginning + Torrenttelik.playlist = Torrenttelik.playlist.split('\xef\xbb\xbf')[1] # garbage at the beginning + Torrenttelik.playlist = Torrenttelik.playlist.replace(',\r\n]}', '\r\n]}') # excess comma at the end except: Torrenttelik.logger.error("Can't download playlist!") return False From fc53911561233475ea0589f22dcad6d31c89377e Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 27 Feb 2016 22:53:30 +0300 Subject: [PATCH 02/95] Fixed HEAD requests handling. Added .pyc and IDE files to .gitignore. --- .gitignore | 5 ++ aceconfig.py | 10 +-- acehttp.py | 12 ++-- plugins/allfon_plugin.py | 5 +- plugins/helloworld_plugin_.py | 6 +- plugins/modules/PluginInterface.py | 2 +- plugins/p2pproxy_plugin.py | 105 +++++++++++++++++++---------- plugins/stat_plugin.py | 6 +- plugins/torrenttelik_plugin.py | 5 +- plugins/torrenttv_plugin.py | 5 +- 10 files changed, 111 insertions(+), 50 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8b377b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +.project +.pydevproject +.settings/ +.idea/ diff --git a/aceconfig.py b/aceconfig.py index 3689f6e..d5dfa90 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -127,29 +127,29 @@ class AceConfig(acedefconfig.AceDefConfig): # !!! # PLEASE set this to 0 if you use VLC # !!! - videodelay = 2 + videodelay = 0 # Obey PAUSE and RESUME commands from Engine # (stops sending data to client, should prevent annoying buffering) # !!! # PLEASE set this to False if you use VLC # !!! - videoobey = True + videoobey = False # Stream send delay after PAUSE/RESUME commands (works only if option # above is enabled) # !!! # PLEASE set this to 0 if you use VLC # !!! - videopausedelay = 2 + videopausedelay = 0 # Seek back feature. # Seeks stream back for specified amount of seconds. # Greatly helps fighing AceSteam lags, but introduces # video stream delay. # Set it to 30 or so. # Works only with the newest versions of AceEngine! - videoseekback = 0 + videoseekback = 30 # Delay before closing Ace Stream connection when client disconnects # In seconds. - videodestroydelay = 3 + videodestroydelay = 0 # Pre-buffering timeout. In seconds. videotimeout = 40 # diff --git a/acehttp.py b/acehttp.py index 9c9446f..f9cab7c 100755 --- a/acehttp.py +++ b/acehttp.py @@ -153,7 +153,7 @@ def do_GET(self, headers_only=False): ''' GET request handler ''' - logger = logging.getLogger('http_HTTPHandler') + logger = logging.getLogger('do_GET') self.clientconnected = True # Don't wait videodestroydelay if error happened self.errorhappened = True @@ -193,7 +193,7 @@ def do_GET(self, headers_only=False): # Handle request with plugin handler if self.reqtype in AceStuff.pluginshandlers: try: - AceStuff.pluginshandlers.get(self.reqtype).handle(self) + AceStuff.pluginshandlers.get(self.reqtype).handle(self, headers_only) except Exception as e: logger.error('Plugin exception: ' + repr(e)) logger.error(traceback.format_exc()) @@ -204,6 +204,8 @@ def do_GET(self, headers_only=False): self.handleRequest(headers_only) def handleRequest(self, headers_only): + logger = logging.getLogger('handleRequest') + # Check if third parameter exists # …/pid/blablablablabla/video.mpg # |_________| @@ -406,9 +408,9 @@ def handleRequest(self, headers_only): AceStuff.clientcounter.delete(self.path_unquoted, self.clientip) if not self.errorhappened and not AceStuff.clientcounter.get(self.path_unquoted): # If no error happened and we are the only client - logger.debug("Sleeping for " + str( - AceConfig.videodestroydelay) + " seconds") - gevent.sleep(AceConfig.videodestroydelay) + if AceConfig.videodestroydelay: + logger.debug("Sleeping for " + str(AceConfig.videodestroydelay) + " seconds") + gevent.sleep(AceConfig.videodestroydelay) if not AceStuff.clientcounter.get(self.path_unquoted): logger.debug("That was the last client, destroying AceClient") if AceConfig.vlcuse: diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py index 4a62108..61bec3a 100644 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -37,7 +37,7 @@ def downloadPlaylist(self): return True - def handle(self, connection): + def handle(self, connection, headers_only=False): # 30 minutes cache if not Allfon.playlist or (int(time.time()) - Allfon.playlisttime > 30 * 60): if not self.downloadPlaylist(): @@ -49,6 +49,9 @@ def handle(self, connection): connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') connection.end_headers() + + if headers_only: + return; # Match playlist with regexp diff --git a/plugins/helloworld_plugin_.py b/plugins/helloworld_plugin_.py index 7d20aa5..00cc3d2 100644 --- a/plugins/helloworld_plugin_.py +++ b/plugins/helloworld_plugin_.py @@ -13,8 +13,12 @@ class Helloworld(AceProxyPlugin): def __init__(self, AceConfig, AceStuff): pass - def handle(self, connection): + def handle(self, connection, headers_only=False): connection.send_response(200) connection.end_headers() + + if headers_only: + return + connection.wfile.write( '

Hello world!

') diff --git a/plugins/modules/PluginInterface.py b/plugins/modules/PluginInterface.py index 378e488..ad65a4c 100644 --- a/plugins/modules/PluginInterface.py +++ b/plugins/modules/PluginInterface.py @@ -15,5 +15,5 @@ class AceProxyPlugin(object): def __init__(self, AceConfig, AceStuff): pass - def handle(self, connection): + def handle(self, connection, headers_only=False): raise NotImplementedError diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index bb4e509..e34b376 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -38,7 +38,7 @@ def __init__(self, AceConfig, AceStuff): super(P2pproxy, self).__init__(AceConfig, AceStuff) self.params = None - def handle(self, connection): + def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Handling request') hostport = connection.headers['Host'] @@ -65,6 +65,12 @@ def handle(self, connection): else: connection.dieWithError() # Bad request return + + if headers_only: + connection.send_response(200) + connection.send_header("Content-Type", "video/mpeg") + connection.end_headers() + return stream_url = None @@ -82,11 +88,13 @@ def handle(self, connection): connection.path = stream_url connection.splittedpath = stream_url.split('/') connection.reqtype = connection.splittedpath[1].lower() - connection.handleRequest(False) + connection.handleRequest(headers_only) elif self.get_param('type') == 'm3u': # /channels/?filter=[filter]&group=[group]&type=m3u - connection.send_response(200) - connection.send_header('Content-Type', 'application/x-mpegurl') - connection.end_headers() + if headers_only: + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.end_headers() + return param_group = self.get_param('group') param_filter = self.get_param('filter') @@ -119,8 +127,19 @@ def handle(self, connection): header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) exported = playlistgen.exportm3u(hostport=hostport, header=header) exported = exported.encode('utf-8') + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.end_headers() connection.wfile.write(exported) else: # /channels/?filter=[filter] + if headers_only: + connection.send_response(200) + connection.send_header('Access-Control-Allow-Origin', '*') + connection.send_header('Connection', 'close') + connection.send_header('Content-Type', 'text/xml;charset=utf-8') + connection.end_headers() + return + param_filter = self.get_param('filter') if not param_filter: param_filter = 'all' # default filter @@ -129,49 +148,57 @@ def handle(self, connection): translations_list = TorrentTvApi.translations(session, param_filter, True) P2pproxy.logger.debug('Exporting') - connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') - connection.send_header('Content-Length', str(len(translations_list))) connection.send_header('Content-Type', 'text/xml;charset=utf-8') + connection.send_header('Content-Length', str(len(translations_list))) connection.end_headers() connection.wfile.write(translations_list) elif connection.reqtype == 'xbmc.pvr': # same as /channels request if len(connection.splittedpath) == 3 and connection.splittedpath[2] == 'playlist': - session = TorrentTvApi.auth(config.email, config.password) - translations_list = TorrentTvApi.translations(session, 'all', True) - - P2pproxy.logger.debug('Exporting') - connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Length', str(len(translations_list))) connection.send_header('Content-Type', 'text/xml;charset=utf-8') connection.end_headers() + + if not headers_only: + return + + session = TorrentTvApi.auth(config.email, config.password) + translations_list = TorrentTvApi.translations(session, 'all', True) + P2pproxy.logger.debug('Exporting') connection.wfile.write(translations_list) elif connection.reqtype == 'archive': # /archive/ branch if len(connection.splittedpath) == 3 and connection.splittedpath[2] == 'channels': # /archive/channels - - session = TorrentTvApi.auth(config.email, config.password) - archive_channels = TorrentTvApi.archive_channels(session, True) - - P2pproxy.logger.debug('Exporting') - connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') - connection.send_header('Content-Length', str(len(archive_channels))) connection.send_header('Content-Type', 'text/xml;charset=utf-8') - connection.end_headers() - connection.wfile.write(archive_channels) + + if headers_only: + connection.end_headers() + else: + session = TorrentTvApi.auth(config.email, config.password) + archive_channels = TorrentTvApi.archive_channels(session, True) + P2pproxy.logger.debug('Exporting') + connection.send_header('Content-Length', str(len(archive_channels))) + connection.end_headers() + connection.wfile.write(archive_channels) if len(connection.splittedpath) == 3 and connection.splittedpath[2].split('?')[ 0] == 'play': # /archive/play?id=[record_id] record_id = self.get_param('id') if not record_id: connection.dieWithError() # Bad request return + + if headers_only: + connection.send_response(200) + connection.send_header("Content-Type", "video/mpeg") + connection.end_headers() + return stream_url = None @@ -189,12 +216,8 @@ def handle(self, connection): connection.path = stream_url connection.splittedpath = stream_url.split('/') connection.reqtype = connection.splittedpath[1].lower() - connection.handleRequest(False) + connection.handleRequest(headers_only) elif self.get_param('type') == 'm3u': # /archive/?type=m3u&date=[param_date]&channel_id=[param_channel] - connection.send_response(200) - connection.send_header('Content-Type', 'application/x-mpegurl') - connection.end_headers() - param_date = self.get_param('date') if not param_date: d = date.today() # consider default date as today if not given @@ -206,18 +229,25 @@ def handle(self, connection): P2pproxy.logger.error('date param is not correct!') connection.dieWithError() return + param_channel = self.get_param('channel_id') if param_channel == '' or not param_channel: P2pproxy.logger.error('Got /archive/ request but no channel_id specified!') connection.dieWithError() return + + if headers_only: + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.end_headers() + return session = TorrentTvApi.auth(config.email, config.password) records_list = TorrentTvApi.records(session, param_channel, d.strftime('%d-%m-%Y')) channels_list = TorrentTvApi.archive_channels(session) - playlistgen = PlaylistGenerator() P2pproxy.logger.debug('Generating archive m3u playlist') + for record in records_list: record_id = record.getAttribute('record_id') name = record.getAttribute('name') @@ -239,6 +269,10 @@ def handle(self, connection): P2pproxy.logger.debug('Exporting') exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True) exported = exported.encode('utf-8') + + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.end_headers() connection.wfile.write(exported) else: # /archive/?date=[param_date]&channel_id=[param_channel] param_date = self.get_param('date') @@ -258,18 +292,21 @@ def handle(self, connection): connection.dieWithError() return - session = TorrentTvApi.auth(config.email, config.password) - records_list = TorrentTvApi.records(session, param_channel, d.strftime('%d-%m-%Y'), True) - - P2pproxy.logger.debug('Exporting') connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') - connection.send_header('Content-Length', str(len(records_list))) connection.send_header('Content-Type', 'text/xml;charset=utf-8') - connection.end_headers() - connection.wfile.write(records_list) + + if headers_only: + connection.end_headers() + else: + session = TorrentTvApi.auth(config.email, config.password) + records_list = TorrentTvApi.records(session, param_channel, d.strftime('%d-%m-%Y'), True) + P2pproxy.logger.debug('Exporting') + connection.send_header('Content-Length', str(len(records_list))) + connection.end_headers() + connection.wfile.write(records_list) def get_param(self, key): if key in self.params: diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index f99b53e..fd1b936 100644 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -13,10 +13,14 @@ def __init__(self, AceConfig, AceStuff): self.config = AceConfig self.stuff = AceStuff - def handle(self, connection): + def handle(self, connection, headers_only=False): connection.send_response(200) connection.send_header('Content-type', 'text/html') connection.end_headers() + + if headers_only: + return + connection.wfile.write( '

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') connection.wfile.write( diff --git a/plugins/torrenttelik_plugin.py b/plugins/torrenttelik_plugin.py index 443cf16..9989318 100644 --- a/plugins/torrenttelik_plugin.py +++ b/plugins/torrenttelik_plugin.py @@ -34,13 +34,16 @@ def downloadPlaylist(self, url): return True - def handle(self, connection): + def handle(self, connection, headers_only=False): hostport = connection.headers['Host'] connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') connection.end_headers() + + if headers_only: + return query = urlparse.urlparse(connection.path).query self.params = urlparse.parse_qs(query) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index d8294db..5a98283 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -44,7 +44,7 @@ def downloadPlaylist(self): return True - def handle(self, connection): + def handle(self, connection, headers_only=False): # 30 minutes cache if not Torrenttv.playlist or (int(time.time()) - Torrenttv.playlisttime > 30 * 60): if not self.downloadPlaylist(): @@ -57,6 +57,9 @@ def handle(self, connection): connection.send_header('Content-Type', 'application/x-mpegurl') connection.end_headers() + if headers_only: + return + # Match playlist with regexp matches = re.finditer(r',(?P\S.+) \((?P.+)\)\n(?P^.+$)', Torrenttv.playlist, re.MULTILINE) From 754d12cdc37569ab0676eb3306774e52e0955ab6 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 29 Feb 2016 20:42:53 +0300 Subject: [PATCH 03/95] Switched to TorrentTV API v3 --- plugins/torrenttv_api.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py index ce5a86d..c3a8515 100644 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -7,6 +7,7 @@ import urllib2 import socket +import random import xml.dom.minidom as dom @@ -44,9 +45,9 @@ def auth(email, password, raw=False): :param raw: if True returns unprocessed data :return: unique session string """ - + xmlresult = TorrentTvApi._result( - 'v2_auth.php?username=' + email + '&password=' + password + '&application=tsproxy') + 'v3/auth.php?username=' + email + '&password=' + password + '&application=tsproxy&guid=' + str(random.randint(100000000,199999999))) if raw: return xmlresult res = TorrentTvApi._check(xmlresult) @@ -65,7 +66,7 @@ def translations(session, translation_type, raw=False): :return: translations list """ xmlresult = TorrentTvApi._result( - 'v2_alltranslation.php?session=' + session + '&type=' + translation_type) + 'v3/translation_list.php?session=' + session + '&type=' + translation_type) if raw: return xmlresult res = TorrentTvApi._check(xmlresult) @@ -84,7 +85,7 @@ def records(session, channel_id, date, raw=False): :return: records list """ xmlresult = TorrentTvApi._result( - 'v2_arc_getrecords.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) + 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) if raw: return xmlresult res = TorrentTvApi._check(xmlresult) @@ -101,7 +102,7 @@ def archive_channels(session, raw=False): :return: archive channels list """ xmlresult = TorrentTvApi._result( - 'v2_arc_getchannels.php?session=' + session) + 'v3/arc_list.php?session=' + session) if raw: return xmlresult res = TorrentTvApi._check(xmlresult) @@ -118,7 +119,7 @@ def stream_source(session, channel_id): :return: type of stream and source """ xmlresult = TorrentTvApi._result( - 'v2_get_stream.php?session=' + session + '&channel_id=' + channel_id) + 'v3/translation_stream.php?session=' + session + '&channel_id=' + channel_id) res = TorrentTvApi._check(xmlresult) stream_type = res.getElementsByTagName('type')[0].firstChild.data source = res.getElementsByTagName('source')[0].firstChild.data @@ -134,7 +135,7 @@ def archive_stream_source(session, record_id): :return: type of stream and source """ xmlresult = TorrentTvApi._result( - 'v2_arc_getstream.php?session=' + session + '&record_id=' + record_id) + 'v3/arc_stream.php?session=' + session + '&record_id=' + record_id) res = TorrentTvApi._check(xmlresult) stream_type = res.getElementsByTagName('type')[0].firstChild.data source = res.getElementsByTagName('source')[0].firstChild.data @@ -167,7 +168,7 @@ def _result(request): :raise: TorrentTvApiException """ try: - result = urllib2.urlopen('http://api.torrent-tv.ru/' + request + '&typeresult=xml', timeout=10).read() + result = urllib2.urlopen('http://1ttvapi.top/' + request + '&typeresult=xml', timeout=10).read() return result except (urllib2.URLError, socket.timeout) as e: - raise TorrentTvApiException('Error happened while trying to access API: ' + repr(e)) \ No newline at end of file + raise TorrentTvApiException('Error happened while trying to access API: ' + repr(e)) From aa48833d1499ab1e7a86378a873d342b4e09fa30 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 9 Mar 2016 23:29:29 +0300 Subject: [PATCH 04/95] Implemented streaming of the same channel to multiple clients without VLC. Channels zapping speedup. --- aceclient/aceclient.py | 139 +++++++++++++++++--- aceclient/clientcounter.py | 133 +++++++++++++------ aceconfig.py | 4 + acehttp.py | 258 ++++++++++++++++++++----------------- plugins/stat_plugin.py | 4 +- 5 files changed, 357 insertions(+), 181 deletions(-) diff --git a/aceclient/aceclient.py b/aceclient/aceclient.py index a6ff115..7066afd 100644 --- a/aceclient/aceclient.py +++ b/aceclient/aceclient.py @@ -4,8 +4,14 @@ import telnetlib import logging import json +import time +import threading +import traceback +import Queue +from collections import deque from acemessages import * - +import aceconfig +from aceconfig import AceConfig class AceException(Exception): @@ -49,15 +55,23 @@ def __init__(self, host, port, connect_timeout=5, result_timeout=10): self._authevent = Event() # Result for getURL() self._urlresult = AsyncResult() + # Result for GETCID() + self._cidresult = AsyncResult() # Event for resuming from PAUSE self._resumeevent = Event() # Seekback seconds. self._seekback = 0 # Did we get START command again? For seekback. self._started_again = False + + self._idleSince = time.time() + self._lock = threading.Condition(threading.Lock()) + self._streamReaderConnection = None + self._streamReaderState = None + self._streamReaderQueue = deque() # Logger - logger = logging.getLogger('AceClient_init') + logger = logging.getLogger('AceClieimport tracebacknt_init') try: self._socket = telnetlib.Telnet(host, port, connect_timeout) @@ -102,6 +116,8 @@ def destroy(self): def _write(self, message): try: + logger = logging.getLogger("AceClient_write") + logger.debug(message) self._socket.write(message + "\r\n") except EOFError as e: raise AceException("Write error! " + repr(e)) @@ -136,18 +152,12 @@ def aceInit(self, gender=AceConst.SEX_MALE, age=AceConst.AGE_18_24, product_key= def _getResult(self): # Logger - logger = logging.getLogger("AceClient_START") - try: result = self._result.get(timeout=self._resulttimeout) if not result: - errmsg = "START error!" - logger.error(errmsg) - raise AceException(errmsg) + raise AceException("Result not received") except gevent.Timeout: - errmsg = "START timeout!" - logger.error(errmsg) - raise AceException(errmsg) + raise AceException("Timeout") return result @@ -159,12 +169,30 @@ def START(self, datatype, value): self._urlresult = AsyncResult() self._write(AceMessage.request.LOADASYNC(datatype.upper(), 0, value)) - contentinfo = self._getResult() + filename = urllib2.unquote(self._getResult().get('files')[0][0]) self._write(AceMessage.request.START(datatype.upper(), value)) self._getResult() - return contentinfo + return filename + + def STOP(self): + ''' + Stop video method + ''' + self._result = AsyncResult() + self._write(AceMessage.request.STOP) + self._getResult() + + def GETCID(self, datatype, url): + self._result = AsyncResult() + self._write(AceMessage.request.LOADASYNC(datatype.upper(), 0, {'url': url})) + contentinfo = self._getResult() + + self._cidresult = AsyncResult() + self._write(AceMessage.request.GETCID(contentinfo.get('checksum'), contentinfo.get('infohash'), 0, 0, 0)) + cid = self._cidresult.get(True, 5) + return '' if not cid or cid == '' else cid[2:] def getUrl(self, timeout=40): # Logger @@ -178,6 +206,75 @@ def getUrl(self, timeout=40): logger.error(errmsg) raise AceException(errmsg) + def startStreamReader(self, url, cid, counter): + logger = logging.getLogger("StreamReader") + self._streamReaderState = None + logger.debug("Opening video stream: %s" % url) + + try: + connection = self._streamReaderConnection = urllib2.urlopen(url) + + if connection.getcode() != 200: + logger.error("Failed to open video stream %s" % connection) + return + + with self._lock: + self._streamReaderState = 1 + self._lock.notifyAll() + + while True: + data = None + clients = counter.getClients(cid) + + try: + data = connection.read(AceConfig.readchunksize) + except: + break; + + if data and clients: + with self._lock: + if len(self._streamReaderQueue) == AceConfig.readcachesize: + self._streamReaderQueue.popleft() + self._streamReaderQueue.append(data) + + for c in clients: + try: + c.addChunk(data, 5.0) + except Queue.Full: + if len(clients) > 1: + logger.debug("Disconnecting client: %s" % str(c)) + c.closeConnection() + elif not clients: + logger.debug("All clients disconnected - closing video stream") + break + else: + logger.warning("No data received") + break + except urllib2.URLError: + logger.error("Failed to open video stream") + logger.error(traceback.format_exc()) + except: + logger.error(traceback.format_exc()) + if counter.getClients(cid): + logger.error("Failed to read video stream") + finally: + self.closeStreamReader() + with self._lock: + self._streamReaderState = 2 + self._lock.notifyAll() + counter.deleteAll(cid) + + def closeStreamReader(self): + logger = logging.getLogger("StreamReader") + c = self._streamReaderConnection + + if c: + self._streamReaderConnection = None + c.close() + logger.debug("Video stream closed") + + self._streamReaderQueue.clear() + def getPlayEvent(self, timeout=None): ''' Blocking while in PAUSE, non-blocking while in RESUME @@ -201,7 +298,7 @@ def _recvData(self): try: self._recvbuffer = self._socket.read_until("\r\n") self._recvbuffer = self._recvbuffer.strip() - #logger.debug('<<< ' + self._recvbuffer) + # logger.debug('<<< ' + self._recvbuffer) except: # If something happened during read, abandon reader. if not self._shuttingDown.isSet(): @@ -216,7 +313,7 @@ def _recvData(self): if 'key=' in self._recvbuffer: self._request_key_begin = self._recvbuffer.find('key=') self._request_key = \ - self._recvbuffer[self._request_key_begin+4:self._request_key_begin+14] + self._recvbuffer[self._request_key_begin + 4:self._request_key_begin + 14] try: self._write(AceMessage.request.READY_key( self._request_key, self._product_key)) @@ -246,8 +343,7 @@ def _recvData(self): self._result.set(False) else: logger.debug("Content info: %s", _contentinfo) - _filename = urllib2.unquote(_contentinfo.get('files')[0][0]) - self._result.set(_filename) + self._result.set(_contentinfo) elif self._recvbuffer.startswith(AceMessage.response.START): # START @@ -290,10 +386,7 @@ def _recvData(self): self._position_last = self._position[2].split('=')[1] self._position_buf = self._position[9].split('=')[1] self._position = self._position[4].split('=')[1] - logger.debug('Current position/last/buf: %s/%s/%s' % (self._position, - self._position_last, - self._position_buf) - ) + if self._seekback and not self._started_again: self._write(AceMessage.request.SEEK(str(int(self._position_last) - \ self._seekback))) @@ -318,6 +411,8 @@ def _recvData(self): AceException(self._status + ' with message ' + self._recvbuffer.split(';')[2])) elif self._status == 'main:starting': self._result.set(True) + elif self._status == 'main:idle': + self._result.set(True) elif self._recvbuffer.startswith(AceMessage.response.PAUSE): logger.debug("PAUSE event") @@ -327,3 +422,7 @@ def _recvData(self): logger.debug("RESUME event") gevent.sleep(self._pausedelay) self._resumeevent.set() + + elif self._recvbuffer.startswith('##') or len(self._recvbuffer) == 0: + self._cidresult.set(self._recvbuffer) + logger.debug("CID: %s" %self._recvbuffer) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index 01ad6f5..43b5fb1 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -1,55 +1,108 @@ ''' Simple Client Counter for VLC VLM ''' - +import threading +import logging +import time +import aceclient +from aceconfig import AceConfig class ClientCounter(object): def __init__(self): + self.lock = threading.RLock() self.clients = dict() - self.aces = dict() + self.idleace = None self.total = 0 + threading.Timer(60.0, self.checkIdle).start() + + def getClients(self, cid): + with self.lock: + return self.clients.get(cid) - def get(self, id): - return self.clients.get(id, (False,))[0] - - def add(self, id, ip): - if self.clients.has_key(id): - self.clients[id][0] += 1 - self.clients[id][1].append(ip) - else: - self.clients[id] = [1, [ip]] - - self.total += 1 - return self.clients[id][0] - - def delete(self, id, ip): - if self.clients.has_key(id): - self.total -= 1 - if self.clients[id][0] == 1: - del self.clients[id] - return False + def add(self, cid, client): + with self.lock: + clients = self.clients.get(cid) + + if clients: + client.ace = clients[0].ace + with client.ace._lock: + client.queue.extend(client.ace._streamReaderQueue) + clients.append(client) else: - self.clients[id][0] -= 1 - self.clients[id][1].remove(ip) - else: - return False + if self.idleace: + client.ace = self.idleace + self.idleace = None + else: + client.ace = self.createAce() + + clients = [client] + self.clients[cid] = clients + + self.total += 1 + return len(clients) - return self.clients[id][0] - - def getAce(self, id): - return self.aces.get(id, False) - - def addAce(self, id, value): - if self.aces.has_key(id): - return False + def delete(self, cid, client): + with self.lock: + if not self.clients.has_key(cid): + return + + clients = self.clients[cid] + + if len(clients) > 1: + clients.remove(client) + else: + del self.clients[cid] + clients[0].ace.closeStreamReader() + + if self.idleace: + client.ace.destroy() + else: + client.ace.STOP() + self.idleace = client.ace + self.idleace._idleSince = time.time() - self.aces[id] = value - return True + client.closeConnection() + self.total -= 1 - def deleteAce(self, id): - if not self.aces.has_key(id): - return False + def deleteAll(self, cid): + with self.lock: + if not self.clients.has_key(cid): + return + + clients = self.clients[cid] + del self.clients[cid] + self.total -= len(clients) + clients[0].ace.closeStreamReader() + + for c in clients: + c.closeConnection() + + if self.idleace: + clients[0].ace.destroy() + else: + clients[0].ace.STOP() + self.idleace = clients[0].ace + self.idleace._idleSince = time.time() - del self.aces[id] - return True + def createAce(self): + logger = logging.getLogger('createAce') + ace = aceclient.AceClient( + AceConfig.acehost, AceConfig.aceport, connect_timeout=AceConfig.aceconntimeout, + result_timeout=AceConfig.aceresulttimeout) + logger.debug("AceClient created") + ace.aceInit( + gender=AceConfig.acesex, age=AceConfig.aceage, + product_key=AceConfig.acekey, pause_delay=AceConfig.videopausedelay, + seekback=AceConfig.videoseekback) + logger.debug("AceClient inited") + return ace + + def checkIdle(self): + with self.lock: + ace = self.idleace + if ace and (ace._idleSince + 5.0 >= time.time()): + self.idleace = None + ace.destroy() + + threading.Timer(60.0, self.checkIdle).start() diff --git a/aceconfig.py b/aceconfig.py index d5dfa90..1cdaedd 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -54,6 +54,10 @@ class AceConfig(acedefconfig.AceDefConfig): httphost = '0.0.0.0' # HTTP Server port httpport = 8000 + # Read the video input stream in chunks of the following size + readchunksize = 8192 + # Cache the following number of the tailing chunks + readcachesize = 1000 # If started as root, drop privileges to this user. # Leave empty to disable. aceproxyuser = '' diff --git a/acehttp.py b/acehttp.py index f9cab7c..c81fc3b 100755 --- a/acehttp.py +++ b/acehttp.py @@ -8,6 +8,7 @@ import traceback import gevent import gevent.monkey +from gevent.queue import Full # Monkeypatching and all the stuff gevent.monkey.patch_all() @@ -21,8 +22,11 @@ import SocketServer from socket import error as SocketException from socket import SHUT_RDWR +from collections import deque +import time +import threading import urllib2 -import hashlib +import Queue import aceclient import aceconfig from aceconfig import AceConfig @@ -42,7 +46,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): requestlist = [] - + def handle_one_request(self): ''' Add request to requestlist, handle request and remove from the list @@ -55,14 +59,16 @@ def closeConnection(self): ''' Disconnecting client ''' - if self.clientconnected: - self.clientconnected = False - try: - self.wfile.close() - self.rfile.close() - self.connection.shutdown(SHUT_RDWR) - except: - pass + with self.lock: + if self.clientconnected: + self.clientconnected = False + try: + self.wfile.close() + self.rfile.close() + self.connection.shutdown(SHUT_RDWR) + except: + pass + self.lock.notify() def dieWithError(self, errorcode=500): ''' @@ -136,8 +142,8 @@ def hangDetector(self): except: pass finally: - self.clientconnected = False logger.debug("Client disconnected") + self.closeConnection() try: self.requestgreenlet.kill() except: @@ -238,7 +244,6 @@ def handleRequest(self, headers_only): self.closeConnection() return - self.path_unquoted = urllib2.unquote(self.splittedpath[2]) # Make list with parameters self.params = list() for i in xrange(3, 8): @@ -246,47 +251,14 @@ def handleRequest(self, headers_only): self.params.append(int(self.splittedpath[i])) except (IndexError, ValueError): self.params.append('0') - - # Adding client to clientcounter - clients = AceStuff.clientcounter.add(self.path_unquoted, self.clientip) - # If we are the one client, but sucessfully got ace from clientcounter, - # then somebody is waiting in the videodestroydelay state - self.ace = AceStuff.clientcounter.getAce(self.path_unquoted) - if not self.ace: - shouldcreateace = True - else: - shouldcreateace = False - - # Use PID as VLC ID if PID requested - # Or torrent url MD5 hash if torrent requested - if self.reqtype == 'pid': - self.vlcid = self.path_unquoted - else: - self.vlcid = hashlib.md5(self.path_unquoted).hexdigest() - - # If we don't use VLC and we're not the first client - if clients != 1 and not AceConfig.vlcuse: - AceStuff.clientcounter.delete(self.path_unquoted, self.clientip) - logger.error( - "Not the first client, cannot continue in non-VLC mode") - self.dieWithError(503) # 503 Service Unavailable - return - - if shouldcreateace: - # If we are the only client, create AceClient - try: - self.ace = aceclient.AceClient( - AceConfig.acehost, AceConfig.aceport, connect_timeout=AceConfig.aceconntimeout, - result_timeout=AceConfig.aceresulttimeout) - # Adding AceClient instance to pool - AceStuff.clientcounter.addAce(self.path_unquoted, self.ace) - logger.debug("AceClient created") - except aceclient.AceException as e: - logger.error("AceClient create exception: " + repr(e)) - AceStuff.clientcounter.delete( - self.path_unquoted, self.clientip) - self.dieWithError(502) # 502 Bad Gateway - return + + self.ace = None + self.lock = threading.Condition(threading.Lock()) + self.queue = deque() + self.path_unquoted = urllib2.unquote(self.splittedpath[2]) + self.cid = self.getCid(self.reqtype, self.path_unquoted) + self.vlcid = self.cid + shouldStart = AceStuff.clientcounter.add(self.cid, self) == 1 # Send fake headers if this User-Agent is in fakeheaderuas tuple if fakeua: @@ -304,29 +276,24 @@ def handleRequest(self, headers_only): gevent.sleep() # Initializing AceClient - if shouldcreateace: - self.ace.aceInit( - gender=AceConfig.acesex, age=AceConfig.aceage, - product_key=AceConfig.acekey, pause_delay=AceConfig.videopausedelay, - seekback=AceConfig.videoseekback) - logger.debug("AceClient inited") + if shouldStart: if self.reqtype == 'pid': - contentinfo = self.ace.START( + self.ace.START( self.reqtype, {'content_id': self.path_unquoted, 'file_indexes': self.params[0]}) elif self.reqtype == 'torrent': paramsdict = dict( zip(aceclient.acemessages.AceConst.START_TORRENT, self.params)) paramsdict['url'] = self.path_unquoted - contentinfo = self.ace.START(self.reqtype, paramsdict) + self.ace.START(self.reqtype, paramsdict) logger.debug("START done") + # Getting URL + self.url = self.ace.getUrl(AceConfig.videotimeout) + # Rewriting host for remote Ace Stream Engine + self.url = self.url.replace('127.0.0.1', AceConfig.acehost) - # Getting URL - self.url = self.ace.getUrl(AceConfig.videotimeout) - # Rewriting host for remote Ace Stream Engine - self.url = self.url.replace('127.0.0.1', AceConfig.acehost) self.errorhappened = False - if shouldcreateace: + if shouldStart: logger.debug("Got url " + self.url) # If using VLC, add this url to VLC if AceConfig.vlcuse: @@ -352,45 +319,67 @@ def handleRequest(self, headers_only): self.url = 'http://' + AceConfig.vlchost + \ ':' + str(AceConfig.vlcoutport) + '/' + self.vlcid logger.debug("VLC url " + self.url) - - # Sending client headers to videostream - self.video = urllib2.Request(self.url) - for key in self.headers.dict: - self.video.add_header(key, self.headers.dict[key]) - - self.video = urllib2.urlopen(self.video) - - # Sending videostream headers to client - if not self.headerssent: - self.send_response(self.video.getcode()) - if self.video.info().dict.has_key('connection'): - del self.video.info().dict['connection'] - if self.video.info().dict.has_key('server'): - del self.video.info().dict['server'] - if self.video.info().dict.has_key('transfer-encoding'): - del self.video.info().dict['transfer-encoding'] - if self.video.info().dict.has_key('keep-alive'): - del self.video.info().dict['keep-alive'] - - for key in self.video.info().dict: - self.send_header(key, self.video.info().dict[key]) - # End headers. Next goes video data - self.end_headers() - logger.debug("Headers sent") - - if not AceConfig.vlcuse: - self.ace.pause() - # Sleeping videodelay - gevent.sleep(AceConfig.videodelay) - self.ace.play() - - # Run proxyReadWrite - self.proxyReadWrite() + + # Sending client headers to videostream + self.video = urllib2.Request(self.url) + for key in self.headers.dict: + self.video.add_header(key, self.headers.dict[key]) + + self.video = urllib2.urlopen(self.video) + + # Sending videostream headers to client + if not self.headerssent: + self.send_response(self.video.getcode()) + if self.video.info().dict.has_key('connection'): + del self.video.info().dict['connection'] + if self.video.info().dict.has_key('server'): + del self.video.info().dict['server'] + if self.video.info().dict.has_key('transfer-encoding'): + del self.video.info().dict['transfer-encoding'] + if self.video.info().dict.has_key('keep-alive'): + del self.video.info().dict['keep-alive'] + + for key in self.video.info().dict: + self.send_header(key, self.video.info().dict[key]) + # End headers. Next goes video data + self.end_headers() + logger.debug("Headers sent") + + # Run proxyReadWrite + self.proxyReadWrite() + else: + if shouldStart: + self.ace._streamReaderState = None + gevent.spawn(self.ace.startStreamReader, self.url, self.cid, AceStuff.clientcounter) + gevent.sleep() + + with self.ace._lock: + while self.clientconnected and self.ace._streamReaderState is None: + self.ace._lock.wait() + if self.clientconnected and self.ace._streamReaderState != 1: + raise urllib2.URLError + + if self.clientconnected: + self.send_response(200) + self.end_headers() + + while self.clientconnected: + data = self.getChunk(60.0) + + if data and self.clientconnected: + try: + self.wfile.write(data) + except: + break + elif self.clientconnected: + logger.debug("No data received in 60 seconds - disconnecting") + break + else: + break # Waiting until hangDetector is joined self.hanggreenlet.join() logger.debug("Request handler finished") - except (aceclient.AceException, vlcclient.VlcException, urllib2.URLError) as e: logger.error("Exception: " + repr(e)) self.errorhappened = True @@ -404,23 +393,53 @@ def handleRequest(self, headers_only): self.errorhappened = True self.dieWithError() finally: + AceStuff.clientcounter.delete(self.cid, self) logger.debug("END REQUEST") - AceStuff.clientcounter.delete(self.path_unquoted, self.clientip) - if not self.errorhappened and not AceStuff.clientcounter.get(self.path_unquoted): - # If no error happened and we are the only client - if AceConfig.videodestroydelay: - logger.debug("Sleeping for " + str(AceConfig.videodestroydelay) + " seconds") - gevent.sleep(AceConfig.videodestroydelay) - if not AceStuff.clientcounter.get(self.path_unquoted): - logger.debug("That was the last client, destroying AceClient") - if AceConfig.vlcuse: + + def addChunk(self, chunk, timeout): + start = time.time() + with self.lock: + while(self.clientconnected and (len(self.queue) == AceConfig.readcachesize)): + remaining = time.time() - start + timeout + if remaining > 0: + self.lock.wait(remaining) + else: + raise Queue.Full + if self.clientconnected: + self.queue.append(chunk) + self.lock.notifyAll() + + def getChunk(self, timeout): + start = time.time() + with self.lock: + while(self.clientconnected and (len(self.queue) == 0)): + remaining = time.time() - start + timeout + if remaining > 0: + self.lock.wait(remaining) + else: + raise Queue.Empty + if self.clientconnected: + chunk = self.queue.popleft() + self.lock.notifyAll() + return chunk + else: + return None + + def getCid(self, reqtype, url): + cid = '' + + if reqtype == 'torrent': + if url[:4].lower() == 'http' : + if url[-8:].lower() == '.acelive' : + AceStuff.clientcounter.lock.acquire() try: - AceStuff.vlcclient.stopBroadcast(self.vlcid) - except: - pass - self.ace.destroy() - AceStuff.clientcounter.deleteAce(self.path_unquoted) - + if not AceStuff.clientcounter.idleace: + AceStuff.clientcounter.idleace = AceStuff.clientcounter.createAce() + cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) + finally: + AceStuff.clientcounter.lock.release() + + return url if cid == '' else cid class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): @@ -433,6 +452,7 @@ class AceStuff(object): ''' Inter-class interaction class ''' + cidcache = dict() # taken from http://stackoverflow.com/questions/2699907/dropping-root-permissions-in-python def drop_privileges(uid_name, gid_name='nogroup'): @@ -512,7 +532,7 @@ def drop_privileges(uid_name, gid_name='nogroup'): DEVNULL = open(os.devnull, 'wb') # Spawning procedures -def spawnVLC(cmd, delay = 0): +def spawnVLC(cmd, delay=0): try: if AceConfig.osplatform == 'Windows' and AceConfig.vlcuseaceplayer: import _winreg @@ -542,7 +562,7 @@ def connectVLC(): print repr(e) return False -def spawnAce(cmd, delay = 0): +def spawnAce(cmd, delay=0): if AceConfig.osplatform == 'Windows': reg = _winreg.ConnectRegistry(None, _winreg.HKEY_CURRENT_USER) try: @@ -629,7 +649,7 @@ def clean_proc(): os.remove(AceStuff.acedir + '\\acestream.port') # This is what we call to stop the server completely -def shutdown(signum = 0, frame = 0): +def shutdown(signum=0, frame=0): logger.info("Stopping server...") # Closing all client connections for connection in server.RequestHandlerClass.requestlist: diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index fd1b936..cf09542 100644 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -26,6 +26,6 @@ def handle(self, connection, headers_only=False): connection.wfile.write( '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') for i in self.stuff.clientcounter.clients: - connection.wfile.write(str(i) + ' : ' + str(self.stuff.clientcounter.clients[i][0]) + ' ' + - str(self.stuff.clientcounter.clients[i][1]) + '
') + for c in self.stuff.clientcounter.clients[i]: + connection.wfile.write(c.clientip + ': ' + c.path_unquoted + '
') connection.wfile.write('') From 95a03382343f2c8e6cd0cda811df3b205f03b502 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 11 Mar 2016 03:27:08 +0300 Subject: [PATCH 05/95] Configurable playlist format --- plugins/config/playlist.py | 7 +++++++ plugins/modules/PlaylistGenerator.py | 24 +++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 plugins/config/playlist.py diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py new file mode 100644 index 0000000..9dbf6ca --- /dev/null +++ b/plugins/config/playlist.py @@ -0,0 +1,7 @@ + +# Default playlist format +m3uemptyheader = '#EXTM3U\n' +m3uheader = \ + '#EXTM3U url-tvg="http://1ttvapi.top/ttv.xmltv.xml.gz"\n' +m3uchanneltemplate = \ + '#EXTINF:-1 group-title="%(group)s" tvg-name="%(tvg)s" tvg-id="%(tvgid)s" tvg-logo="%(logo)s",%(name)s\n#EXTGRP:%(group)s\n%(url)s\n' diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py index 8be7dd5..7742383 100644 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -5,15 +5,10 @@ ''' import re import urllib2 +import plugins.config.playlist as config class PlaylistGenerator(object): - m3uheader = \ - '#EXTM3U url-tvg="http://www.teleguide.info/download/new3/jtv.zip"\n' - m3uemptyheader = '#EXTM3U\n' - m3uchanneltemplate = \ - '#EXTINF:-1 group-title="%s" tvg-name="%s" tvg-id="%s" tvg-logo="%s",%s\n%s\n' - def __init__(self): self.itemlist = list() @@ -35,9 +30,16 @@ def _generatem3uline(item): ''' Generates EXTINF line with url ''' - return PlaylistGenerator.m3uchanneltemplate % ( - item.get('group', ''), item.get('tvg', ''), item.get('tvgid', ''), - item.get('logo', ''), item.get('name'), item.get('url')) + if not item.has_key('tvg'): + item['tvg'] = '' + if not item.has_key('tvgid'): + item['tvgid'] = '' + if not item.has_key('group'): + item['group'] = '' + if not item.has_key('logo'): + item['logo'] = '' + + return config.m3uchanneltemplate % item def exportm3u(self, hostport, add_ts=False, empty_header=False, archive=False, header=None): ''' @@ -46,9 +48,9 @@ def exportm3u(self, hostport, add_ts=False, empty_header=False, archive=False, h if header is None: if not empty_header: - itemlist = PlaylistGenerator.m3uheader + itemlist = config.m3uheader else: - itemlist = PlaylistGenerator.m3uemptyheader + itemlist = config.m3uemptyheader if add_ts: # Adding ts:// after http:// for some players hostport = 'ts://' + hostport From d7119f7cb38c576c864de60b1dc713cef48e8dd0 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 12 Mar 2016 02:21:21 +0300 Subject: [PATCH 06/95] Fixes and enhancements --- aceclient/aceclient.py | 22 ++-- aceclient/clientcounter.py | 70 +++++++----- aceconfig.py | 20 ++-- acedefconfig.py | 3 - acehttp.py | 218 ++++++++++++++++++++++--------------- plugins/stat_plugin.py | 2 +- 6 files changed, 192 insertions(+), 143 deletions(-) diff --git a/aceclient/aceclient.py b/aceclient/aceclient.py index 7066afd..8abc605 100644 --- a/aceclient/aceclient.py +++ b/aceclient/aceclient.py @@ -165,24 +165,18 @@ def START(self, datatype, value): ''' Start video method ''' - self._result = AsyncResult() self._urlresult = AsyncResult() - - self._write(AceMessage.request.LOADASYNC(datatype.upper(), 0, value)) - filename = urllib2.unquote(self._getResult().get('files')[0][0]) - self._write(AceMessage.request.START(datatype.upper(), value)) self._getResult() - return filename - def STOP(self): ''' Stop video method ''' - self._result = AsyncResult() - self._write(AceMessage.request.STOP) - self._getResult() + if self._state is not None and self._state != '0': + self._result = AsyncResult() + self._write(AceMessage.request.STOP) + self._getResult() def GETCID(self, datatype, url): self._result = AsyncResult() @@ -208,7 +202,7 @@ def getUrl(self, timeout=40): def startStreamReader(self, url, cid, counter): logger = logging.getLogger("StreamReader") - self._streamReaderState = None + self._streamReaderState = 1 logger.debug("Opening video stream: %s" % url) try: @@ -219,7 +213,7 @@ def startStreamReader(self, url, cid, counter): return with self._lock: - self._streamReaderState = 1 + self._streamReaderState = 2 self._lock.notifyAll() while True: @@ -243,7 +237,7 @@ def startStreamReader(self, url, cid, counter): except Queue.Full: if len(clients) > 1: logger.debug("Disconnecting client: %s" % str(c)) - c.closeConnection() + c.destroy() elif not clients: logger.debug("All clients disconnected - closing video stream") break @@ -260,7 +254,7 @@ def startStreamReader(self, url, cid, counter): finally: self.closeStreamReader() with self._lock: - self._streamReaderState = 2 + self._streamReaderState = 3 self._lock.notifyAll() counter.deleteAll(cid) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index 43b5fb1..567f734 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -23,6 +23,7 @@ def getClients(self, cid): def add(self, cid, client): with self.lock: clients = self.clients.get(cid) + self.total += 1 if clients: client.ace = clients[0].ace @@ -39,7 +40,6 @@ def add(self, cid, client): clients = [client] self.clients[cid] = clients - self.total += 1 return len(clients) def delete(self, cid, client): @@ -49,41 +49,51 @@ def delete(self, cid, client): clients = self.clients[cid] - if len(clients) > 1: - clients.remove(client) - else: + if client not in clients: + return + + try: + if len(clients) > 1: + clients.remove(client) + else: + del self.clients[cid] + clients[0].ace.closeStreamReader() + + if self.idleace: + client.ace.destroy() + else: + client.ace.STOP() + self.idleace = client.ace + self.idleace._idleSince = time.time() + self.idleace._streamReaderState = None + finally: + self.total -= 1 + + def deleteAll(self, cid): + clients = None + + try: + with self.lock: + if not self.clients.has_key(cid): + return + + clients = self.clients[cid] del self.clients[cid] + self.total -= len(clients) clients[0].ace.closeStreamReader() - + if self.idleace: - client.ace.destroy() + clients[0].ace.destroy() else: - client.ace.STOP() - self.idleace = client.ace + clients[0].ace.STOP() + self.idleace = clients[0].ace self.idleace._idleSince = time.time() + self.idleace._streamReaderState = None + finally: + if clients: + for c in clients: + c.destroy() - client.closeConnection() - self.total -= 1 - - def deleteAll(self, cid): - with self.lock: - if not self.clients.has_key(cid): - return - - clients = self.clients[cid] - del self.clients[cid] - self.total -= len(clients) - clients[0].ace.closeStreamReader() - - for c in clients: - c.closeConnection() - - if self.idleace: - clients[0].ace.destroy() - else: - clients[0].ace.STOP() - self.idleace = clients[0].ace - self.idleace._idleSince = time.time() def createAce(self): logger = logging.getLogger('createAce') diff --git a/aceconfig.py b/aceconfig.py index 1cdaedd..13eacd8 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -42,9 +42,7 @@ class AceConfig(acedefconfig.AceDefConfig): # Ace Stream Engine connection timeout aceconntimeout = 5 # Ace Stream Engine authentication result timeout - aceresulttimeout = 10 - # Message level (DEBUG, INFO, WARNING, ERROR, CRITICAL) - debug = logging.DEBUG + aceresulttimeout = 5 # # ---------------------------------------------------- # AceProxy configuration @@ -73,10 +71,6 @@ class AceConfig(acedefconfig.AceDefConfig): ) # Maximum concurrent connections (video clients) maxconns = 10 - # Logging to a file - loggingtoafile = False - # Path for logs, default is current directory. For example '/tmp/' - logpath = '' # # ---------------------------------------------------- # VLC configuration @@ -169,3 +163,15 @@ class AceConfig(acedefconfig.AceDefConfig): fakeheaderuas = ('HLS Client/2.0 (compatible; LG NetCast.TV-2012)', 'Mozilla/5.0 (DirectFB; Linux armv7l) AppleWebKit/534.26+ (KHTML, like Gecko) Version/5.0 Safari/534.26+ LG Browser/5.00.00(+mouse+3D+SCREEN+TUNER; LGE; 42LM670T-ZA; 04.41.03; 0x00000001;); LG NetCast.TV-2012 0' ) + + # Logging configuration + # + # Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) + loglevel = logging.DEBUG + # Log message format + logfmt = '%(asctime)s %(levelname)s %(threadName)s %(filename)s:%(lineno)d %(name)s| %(message)s' + # Log date format + logdatefmt='%d.%m %H:%M:%S' + # Full path to a log file + logfile = None + \ No newline at end of file diff --git a/acedefconfig.py b/acedefconfig.py index c9a35d1..745edfc 100644 --- a/acedefconfig.py +++ b/acedefconfig.py @@ -20,7 +20,6 @@ class AceDefConfig(object): acestartuptimeout = 10 aceconntimeout = 5 aceresulttimeout = 10 - debug = logging.DEBUG # httphost = '0.0.0.0' httpport = 8000 @@ -32,8 +31,6 @@ class AceDefConfig(object): '192.168.0.0/16', ) maxconns = 10 - loggingtoafile = False - logpath = '' vlcuse = False vlcuseaceplayer = False vlcspawn = False diff --git a/acehttp.py b/acehttp.py index c81fc3b..f7cf1a7 100755 --- a/acehttp.py +++ b/acehttp.py @@ -59,23 +59,21 @@ def closeConnection(self): ''' Disconnecting client ''' - with self.lock: - if self.clientconnected: - self.clientconnected = False - try: - self.wfile.close() - self.rfile.close() - self.connection.shutdown(SHUT_RDWR) - except: - pass - self.lock.notify() + if self.connected: + self.connected = False + try: + self.wfile.close() + self.rfile.close() + self.connection.shutdown(SHUT_RDWR) + except: + pass def dieWithError(self, errorcode=500): ''' Close connection with error ''' logging.warning("Dying with error") - if self.clientconnected: + if self.connected: self.send_error(errorcode) self.end_headers() self.closeConnection() @@ -94,7 +92,7 @@ def proxyReadWrite(self): while True: if AceConfig.videoobey and not AceConfig.vlcuse: # Wait for PlayEvent if videoobey is enabled. Not for VLC - self.ace.getPlayEvent() + self.client.ace.getPlayEvent() if AceConfig.videoobey and AceConfig.vlcuse: # For VLC @@ -102,7 +100,7 @@ def proxyReadWrite(self): # flag is not set), pause the stream if AceEngine says so and # we should obey it. # A bit ugly, huh? - self.streamstate = self.ace.getPlayEvent(0.5) + self.streamstate = self.client.ace.getPlayEvent(0.5) if self.streamstate and not self.vlcstate: AceStuff.vlcclient.playBroadcast(self.vlcid) self.vlcstate = True @@ -112,12 +110,12 @@ def proxyReadWrite(self): AceStuff.vlcclient.pauseBroadcast(self.vlcid) self.vlcstate = False - if not self.clientconnected: + if not self.connected: logger.debug("Client is not connected, terminating") break data = self.video.read(4096) - if data and self.clientconnected: + if data and self.connected: self.wfile.write(data) else: logger.warning("Video connection closed") @@ -127,7 +125,7 @@ def proxyReadWrite(self): logger.warning("Video connection dropped") finally: self.video.close() - self.closeConnection() + self.client.destroy() def hangDetector(self): ''' @@ -143,7 +141,10 @@ def hangDetector(self): pass finally: logger.debug("Client disconnected") - self.closeConnection() + client = self.client + if client: + self.client.destroy() + try: self.requestgreenlet.kill() except: @@ -160,7 +161,8 @@ def do_GET(self, headers_only=False): GET request handler ''' logger = logging.getLogger('do_GET') - self.clientconnected = True + self.reqtime = time.time() + self.connected = True # Don't wait videodestroydelay if error happened self.errorhappened = True # Headers sent flag for fake headers UAs @@ -252,13 +254,13 @@ def handleRequest(self, headers_only): except (IndexError, ValueError): self.params.append('0') - self.ace = None - self.lock = threading.Condition(threading.Lock()) - self.queue = deque() + self.url = None self.path_unquoted = urllib2.unquote(self.splittedpath[2]) - self.cid = self.getCid(self.reqtype, self.path_unquoted) - self.vlcid = self.cid - shouldStart = AceStuff.clientcounter.add(self.cid, self) == 1 + cid = self.getCid(self.reqtype, self.path_unquoted) + logger.debug("CID: " + cid) + self.client = Client(cid, self) + self.vlcid = cid + shouldStart = AceStuff.clientcounter.add(cid, self.client) == 1 # Send fake headers if this User-Agent is in fakeheaderuas tuple if fakeua: @@ -278,16 +280,16 @@ def handleRequest(self, headers_only): # Initializing AceClient if shouldStart: if self.reqtype == 'pid': - self.ace.START( + self.client.ace.START( self.reqtype, {'content_id': self.path_unquoted, 'file_indexes': self.params[0]}) elif self.reqtype == 'torrent': paramsdict = dict( zip(aceclient.acemessages.AceConst.START_TORRENT, self.params)) paramsdict['url'] = self.path_unquoted - self.ace.START(self.reqtype, paramsdict) + self.client.ace.START(self.reqtype, paramsdict) logger.debug("START done") # Getting URL - self.url = self.ace.getUrl(AceConfig.videotimeout) + self.url = self.client.ace.getUrl(AceConfig.videotimeout) # Rewriting host for remote Ace Stream Engine self.url = self.url.replace('127.0.0.1', AceConfig.acehost) @@ -303,10 +305,10 @@ def handleRequest(self, headers_only): else: self.vlcprefix = '' - self.ace.pause() + self.client.ace.pause() # Sleeping videodelay gevent.sleep(AceConfig.videodelay) - self.ace.play() + self.client.ace.play() AceStuff.vlcclient.startBroadcast( self.vlcid, self.vlcprefix + self.url, AceConfig.vlcmux, AceConfig.vlcpreaccess) @@ -348,34 +350,8 @@ def handleRequest(self, headers_only): # Run proxyReadWrite self.proxyReadWrite() else: - if shouldStart: - self.ace._streamReaderState = None - gevent.spawn(self.ace.startStreamReader, self.url, self.cid, AceStuff.clientcounter) - gevent.sleep() - - with self.ace._lock: - while self.clientconnected and self.ace._streamReaderState is None: - self.ace._lock.wait() - if self.clientconnected and self.ace._streamReaderState != 1: - raise urllib2.URLError + self.client.handle(shouldStart, self.url) - if self.clientconnected: - self.send_response(200) - self.end_headers() - - while self.clientconnected: - data = self.getChunk(60.0) - - if data and self.clientconnected: - try: - self.wfile.write(data) - except: - break - elif self.clientconnected: - logger.debug("No data received in 60 seconds - disconnecting") - break - else: - break # Waiting until hangDetector is joined self.hanggreenlet.join() @@ -393,60 +369,124 @@ def handleRequest(self, headers_only): self.errorhappened = True self.dieWithError() finally: - AceStuff.clientcounter.delete(self.cid, self) - logger.debug("END REQUEST") + try: + AceStuff.clientcounter.delete(cid, self.client) + self.client.destroy() + self.ace = None + self.client = None + logger.debug("END REQUEST") + except: + logger.error(traceback.format_exc()) + + def getCid(self, reqtype, url): + cid = '' + + if reqtype == 'torrent': + if url[:4].lower() == 'http' : + if url[-8:].lower() == '.acelive' : + with AceStuff.clientcounter.lock: + if not AceStuff.clientcounter.idleace: + AceStuff.clientcounter.idleace = AceStuff.clientcounter.createAce() + cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) + + return url if cid == '' else cid + + +class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + + def handle_error(self, request, client_address): + # Do not print HTTP tracebacks + pass + + +class Client: + + def __init__(self, cid, handler): + self.cid = cid + self.handler = handler + self.ace = None + self.lock = threading.Condition(threading.Lock()) + self.queue = deque() + def handle(self, shouldStart, url): + logger = logging.getLogger("ClientHandler") + + if shouldStart: + self.ace._streamReaderState = 1 + gevent.spawn(self.ace.startStreamReader, url, self.cid, AceStuff.clientcounter) + gevent.sleep() + + with self.ace._lock: + start = time.time() + while self.handler.connected and self.ace._streamReaderState == 1: + remaining = start + 5.0 - time.time() + if remaining > 0: + self.ace._lock.wait(remaining) + else: + logger.warning("Video stream not opened in 5 seconds - disconnecting") + self.handler.dieWithError() + return + + if self.handler.connected and self.ace._streamReaderState != 2: + logger.warning("No video stream found") + self.handler.dieWithError() + return + + if self.handler.connected: + self.handler.send_response(200) + self.handler.end_headers() + + while self.handler.connected and self.ace._streamReaderState == 2: + data = self.getChunk(60.0) + + if data and self.handler.connected: + try: + self.handler.wfile.write(data) + except: + break + elif self.handler.connected: + logger.debug("No data received in 60 seconds - disconnecting") + break + else: + break + def addChunk(self, chunk, timeout): start = time.time() with self.lock: - while(self.clientconnected and (len(self.queue) == AceConfig.readcachesize)): - remaining = time.time() - start + timeout + while(self.handler.connected and (len(self.queue) == AceConfig.readcachesize)): + remaining = start + timeout + time.time() if remaining > 0: self.lock.wait(remaining) else: raise Queue.Full - if self.clientconnected: + if self.handler.connected: self.queue.append(chunk) self.lock.notifyAll() def getChunk(self, timeout): start = time.time() with self.lock: - while(self.clientconnected and (len(self.queue) == 0)): - remaining = time.time() - start + timeout + while(self.handler.connected and (len(self.queue) == 0)): + remaining = start + timeout - time.time() if remaining > 0: self.lock.wait(remaining) else: raise Queue.Empty - if self.clientconnected: + if self.handler.connected: chunk = self.queue.popleft() self.lock.notifyAll() return chunk else: return None - - def getCid(self, reqtype, url): - cid = '' + + def destroy(self): + with self.lock: + self.handler.closeConnection() + self.lock.notifyAll() + self.queue.clear() - if reqtype == 'torrent': - if url[:4].lower() == 'http' : - if url[-8:].lower() == '.acelive' : - AceStuff.clientcounter.lock.acquire() - try: - if not AceStuff.clientcounter.idleace: - AceStuff.clientcounter.idleace = AceStuff.clientcounter.createAce() - cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) - finally: - AceStuff.clientcounter.lock.release() - - return url if cid == '' else cid - -class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): - - def handle_error(self, request, client_address): - # Do not print HTTP tracebacks - pass - + def __eq__(self, other): + return self is other class AceStuff(object): ''' @@ -479,8 +519,10 @@ def drop_privileges(uid_name, gid_name='nogroup'): return False logging.basicConfig( - filename=AceConfig.logpath + 'acehttp.log' if AceConfig.loggingtoafile else None, - format='%(asctime)s %(levelname)s %(name)s: %(message)s', datefmt='%d.%m.%Y %H:%M:%S', level=AceConfig.debug) + level=AceConfig.loglevel, + filename=AceConfig.logfile, + format=AceConfig.logfmt, + datefmt=AceConfig.logdatefmt) logger = logging.getLogger('INIT') # Loading plugins diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index cf09542..9a42c07 100644 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -27,5 +27,5 @@ def handle(self, connection, headers_only=False): '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') for i in self.stuff.clientcounter.clients: for c in self.stuff.clientcounter.clients[i]: - connection.wfile.write(c.clientip + ': ' + c.path_unquoted + '
') + connection.wfile.write(c.handler.clientip + ': ' + c.handler.path_unquoted + '
') connection.wfile.write('') From bd25c7eb00a7a0d58a8d8e31ea444cbfe1b2bf4c Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 12 Mar 2016 15:14:35 +0300 Subject: [PATCH 07/95] Destroy ace if failed to stop --- aceclient/clientcounter.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index 567f734..ab487c8 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -62,10 +62,13 @@ def delete(self, cid, client): if self.idleace: client.ace.destroy() else: - client.ace.STOP() - self.idleace = client.ace - self.idleace._idleSince = time.time() - self.idleace._streamReaderState = None + try: + client.ace.STOP() + self.idleace = client.ace + self.idleace._idleSince = time.time() + self.idleace._streamReaderState = None + except: + client.ace.destroy() finally: self.total -= 1 @@ -85,10 +88,13 @@ def deleteAll(self, cid): if self.idleace: clients[0].ace.destroy() else: - clients[0].ace.STOP() - self.idleace = clients[0].ace - self.idleace._idleSince = time.time() - self.idleace._streamReaderState = None + try: + clients[0].ace.STOP() + self.idleace = clients[0].ace + self.idleace._idleSince = time.time() + self.idleace._streamReaderState = None + except: + clients[0].ace.destroy() finally: if clients: for c in clients: From b7947aac92eb32186d021697de37176642b526e4 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 13 Mar 2016 00:14:53 +0300 Subject: [PATCH 08/95] Use WEB API if failed to get CID from engine --- acehttp.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/acehttp.py b/acehttp.py index f7cf1a7..8835366 100755 --- a/acehttp.py +++ b/acehttp.py @@ -23,6 +23,8 @@ from socket import error as SocketException from socket import SHUT_RDWR from collections import deque +import base64 +import json import time import threading import urllib2 @@ -387,7 +389,21 @@ def getCid(self, reqtype, url): with AceStuff.clientcounter.lock: if not AceStuff.clientcounter.idleace: AceStuff.clientcounter.idleace = AceStuff.clientcounter.createAce() - cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) + try: + cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) + except: + pass + + if cid == '': + try: + logging.debug("Failed to get CID from engine. Trying WEB API.") + req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) + f = urllib2.urlopen(req, timeout=3).read() + req = urllib2.Request('http://api.torrentstream.net/upload/raw', base64.b64encode(f)) + req.add_header('Content-Type', 'application/octet-stream') + cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] + except: + logging.debug("Failed to get CID from WEB API") return url if cid == '' else cid From c75055b7789d8d6cf3d633e52190c1e0852c18da Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 13 Mar 2016 00:56:41 +0300 Subject: [PATCH 09/95] Added new handler - logos which is used to generate logomap for the torrenttv plugin. Updated logomap of the torrenttv plugin. --- plugins/config/torrenttv.py | 467 ++++++++++++++++++++---------------- plugins/p2pproxy_plugin.py | 22 +- 2 files changed, 276 insertions(+), 213 deletions(-) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index d44d032..7f52c98 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -23,383 +23,417 @@ logobase = 'http://torrent-tv.ru/uploads/' logomap = { u'1 HD': logobase + 'FtLnmUwjG18XJFEKYvLKjwUq1gwHVZ.png', - u'1 Балтийский Музыкальный': logobase + 'wLtopqioazWFEqSAGxC1D8ybC0KvHq.png', + u'1 Балтийский музыкальный': logobase + 'qOr6q3gTZcbIzbUUduk3pIp2wh6uG3.png', + u'1 канал (Израиль)': logobase + 'jdPZiDGCTrwqm9DOyjWqhx8fsZNnzU.png', u'1+1': logobase + 'omm2Xc8xSVIT6Od6ca4QqMrEXw3jaK.png', + u'1+1 International': logobase + 'o6wtkD3LF7NT4yADrcoBrXVWDDrvgV.png', u'100% News': logobase + '9yEWvPmTcFS8lyQ5NjJ7vbYOa3bx1W.png', - u'112 Украина HD': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'112 Украина': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', + u'112 Украина HD': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', + u'12 Канал (Омск)': logobase + '6DFMdGjXUjvzyk3B5ltbEd8mWVxAcD.png', + u'2 канал (Израиль)': logobase + 'b8SuI3XcVDLAM39jRemlcwcgb6Snuo.png', u'2+2': logobase + 'XHXBC3ghvhh100BNXylSgJLx5FVQgD.png', u'24 Док': logobase + 'H1UXBai10DjYfScfv1sNAILV9EPDer.png', u'24 Украина': logobase + 'XfKEdfsy4S1zbE8n2tB1tNNe9IkrRP.png', - u'2x2 (+2)': logobase + 'hTpJUV15GSTxZ5kJQGLcn42kCzKyEH.png', u'2x2': logobase + 'hTpJUV15GSTxZ5kJQGLcn42kCzKyEH.png', - u'360 градусов HD': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', + u'2x2 (+2)': logobase + 'ZPOhZle6vrDaulo2KMmyrCkkkLn7Ci.png', + u'2x2 (+5)': logobase + 'WMlvhNmhzmk2JgKct8Oa5FcWHmR7fZ.png', u'360 градусов': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', + u'360 градусов HD': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', + u'365 Music HD': logobase + 'qptiMVru1g26wTj9ZrfLBhbeeVBIB5.png', u'365 Дней': logobase + '0IZrVwoxtmjtgnWu5Dj4Hb8FRc8NIX.png', + u'3S TV': logobase + 'AL7uQOgsyYqE7V1BagxC2jqQVOvucp.png', u'43 Канал HD': logobase + '1mNoU1QXmcmK48eVTlnADtsEBmA2sN.png', - u'49 канал': logobase + 'liRZcIcNbzQTngm8CIkcnw7gLJfeIJ.png', u'5 канал (Украина)': logobase + '9La0uS6S8rMKr0BOh6vSCQLNiCqN7N.png', u'8 канал': logobase + 'wKPUJjcpZIfI5zBSZwNayOlv63zRNV.png', u'9 волна': logobase + '8zDeTSBhsmJDbXo9dxHA1c9mDOP9sP.png', - u'A-One UA': logobase + '8rEKNCXhKeWnUvGhWCsrb2RN5FMqJi.png', u'A-One': logobase + 'buHzdmouswQyLnhzwqnPUHdS9BMHTw.png', - u'AMC': logobase + 'rozrC8ZPYA1YGajyow35doeVcaQzpa.png', - u'ATR': logobase + 'uggqxQxxDTuz1P1mY4PZdlRcAKow3F.png', + u'A-One UA': logobase + '8rEKNCXhKeWnUvGhWCsrb2RN5FMqJi.png', u'Al Jazeera English': logobase + 'B1AC3jl0CY8u4qxO0aIEHVMFQFvBUu.png', - u'Amazing Life': logobase + 'w8TNEcKxT64e1cJuN1b1DXvV1IumE0.png', + u'AMC': logobase + 'rozrC8ZPYA1YGajyow35doeVcaQzpa.png', u'Amedia 1': logobase + 'jT3vAEOG5jTd2t8GcC797Bw5W0kSl9.png', u'Amedia 2': logobase + 'fAvxTQbWu0DAcMkqej0m73KohAcQJw.png', - u'Amedia Premium HD (SD)': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', + u'Amedia Hit HD': logobase + 'ujYSHmxPViAzjm4yc1YPi42hju7vcq.png', u'Amedia Premium HD': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', + u'Amedia Premium HD (SD)': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Ani': logobase + 'vui1cRrE05CZv1N9Qb20jJ6mTFOJue.png', u'Animal Planet': logobase + '45.png', u'Animаl Planеt HD': logobase + '9HZGan5rQItVQOfnB91FGqyJXjoqYV.png', - u'Armenia 1': logobase + 'YST19D8Vt4g7ck1MquvppzKy8Oa27D.png', + u'Anyday 3D': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', + u'Anyday HD': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', + u'Arena Sport 1': logobase + 'EK2c3dIiDNuYQ9zdlASOTaJabNf4gI.png', + u'Arena Sport 2': logobase + 'QWlwDu4FRIilHsC6ZFR6BdFW8qtvnb.png', + u'Arena Sport 3': logobase + 'x31HEc9Vo87VdvF1eXQTl61ZXgq7HY.png', u'Armenia TV Satellite': logobase + 'M4UAMT6cEyL3zuim9fzy38vZRkIfKq.png', + u'ATR': logobase + 'uggqxQxxDTuz1P1mY4PZdlRcAKow3F.png', u'Az TV': logobase + 'FB2qxgNm8xTU6YiR2fY1jH4PIRR2SF.png', u'B4U Music UK': logobase + 'LlfDsq3FEU2D4P3lmj25fvl6zQnvNM.png', + u'BabyTV': logobase + 'BXLto7eBgsRpbGP6Cb8tgIfh4h5RiK.png', + u'Balticum Auksinis': logobase + 'dJVlqa8Uy1m75CUODkV70yN5P9GFjt.png', + u'Balticum Platinum': logobase + 'c0hXvCfIFAs4Nt4SSGmfZ5DMZYg4jJ.png', u'BBC One': logobase + 'ofuDCyM7MgzwFbzG2pyavFu6xeRHwp.png', u'BBC Two': logobase + 'o7LyIuTJT2C7wJG77Zx5cZhBYP2L8B.png', u'BBC World News': logobase + '8cSYWwvq6BAzcGDjJODvCGQYGJ2c4S.png', - u'BT Sport 1': logobase + 'RNwudSF1Lys88WmIXhiS43g2urfknl.png', - u'BT Sport 2': logobase + 'NdEFtAJBS9EtR2rSEUZ1ZF15tQzH6y.png', - u'BT Sport ESPN': logobase + 'NA9uK6s2dqxbRAuhguG80gO03mGUOl.png', - u'BT Sport Europe': logobase + 'e3dk71TnjMRLy1A1FjvWnuwLDP3rM0.png', - u'BTV 1 HD': logobase + 's9FERVp6FKtSyMB0N4g17aaKLdxGl5.png', - u'Bein Sports 1 HD France': logobase + 'iFvQJFlFSlOnL7VN1KZ2qo5DD6lNgU.png', - u'Bein Sports 2 HD France': logobase + 'eYy8eQWvjK2lUBRCPXldM7DgOanhVZ.png', u'Bloomberg': logobase + 'OTsoNZRT8xjXz5nnTICPCQRvNLjBal.png', + u'Blue Hustler': logobase + 'dFDAUzpGhQFOyHz59iyi7gsGHHMQbj.png', + u'Bollywood HD': logobase + 'HXzWxtMxmXkrpgr88Kh8Z1A8F6o5dK.png', u'Boomerang': logobase + 'tsP2U3zkp5o8B0TD6luRLg0leS9FvM.png', - u'Boxnation': logobase + '8mECbEuuVmoMl17GPq6hV8X7LDMlu5.png', u'Brazzers TV Europe': logobase + 'OmVBp3Kz4Lx722dq2e1OxE26QSxrDA.png', u'Bridge TV': logobase + 'dPhBiaViIznwjeWU3pTbIXFmS3iJkU.png', u'Brodilo TV HD': logobase + '0pLPWiGqZjDe2O4vbPd8QL0qGsA9lq.png', + u'BTV': logobase + '52ZaTijyyDOsKI7B1fYnUWliW6i2ah.png', u'Business': logobase + 'wvwXc2x8Bjev90GD6LO6t7TL2aKW1h.png', u'C More Tennis': logobase + 'ql4qy7gHN4GtWhcqfd97HqXUs8HXI9.png', - u'CBS Drama': logobase + '1Mcq9jbl7qPeXpT0bbPTnf8aM3f7dq.png', - u'CCTV 4': logobase + 'PsrMNDhKrEgCJA5lAR9UUtBlOI4uJg.png', - u'CCTV News': logobase + 'TyWVFvKwV3lOYYXWVJSj8HMu5AodJr.png', - u'CNL': logobase + 'wwQ6u4lFXyaUDJLu2JOh6mtbkIz0Nf.png', - u'CNN International': logobase + 'lIwbRbM3ve4ixhFXp75Oap9nzcO71G.png', - u'CT 1': logobase + 'sgPGofRHRzOlNb6dBsk5QGiCdzJewS.png', - u'CT 2': logobase + 'dFDWTyqFwS3roF7Jb3JGZIfm1htuUv.png', - u'CT Decko': logobase + 'iZ4GvLdo9Gt4NiVLCuiRYtF8rtgYmi.png', - u'Canal+ Deportes 2': logobase + '4EUoskZorTXBkMxeR8ykSpyQOErvgu.png', + u'C Music TV HD': logobase + 'YhtkJhsV8NKwm4FGWhQZDmYcW0TYQS.png', u'Canal+ Deportes': logobase + 'qoi5H4OujPPtCC1JJO1ozeyFLPqPk6.png', + u'Canal+ Deportes 2': logobase + '4EUoskZorTXBkMxeR8ykSpyQOErvgu.png', u'Canal+ Futbol': logobase + '1En2KRyo8ywbMPWjiiri7TcEXJbqcE.png', u'Canal+ Liga': logobase + '6GyapitmDBshZmCxXPJsQWJGhkcooJ.png', u'Candy': logobase + 'YW8BJnImniuR7l1U85Khw31mX2XO0S.png', u'Candyman': logobase + '8pCxUJ8TBWfvWCrIPN4a4Jimsv9onx.png', u'Cartoon Network': logobase + 'NTNQLLri3Hh9iqYjW7VEkFYJsTLjk9.png', + u'CBS Drama': logobase + '1Mcq9jbl7qPeXpT0bbPTnf8aM3f7dq.png', + u'CBS Reality': logobase + 'QbWaD2vNgLRLY0Hsccg3nf9azAXRld.png', + u'CCTV 9 Documentary': logobase + 'dqoArqO8dd09vzsSSQPqLUj5wM9O9M.png', + u'CCTV News': logobase + 'TyWVFvKwV3lOYYXWVJSj8HMu5AodJr.png', + u'CCTV Русский': logobase + 'DcaE92lKRSDFBPlYUnsNRauFLbotKu.png', u'CentoXCento TV': logobase + 'dPuPlqJtkLMRy7NmOhyHDfL0PJqYj4.png', - u'Cinemax 1': logobase + 'iVIDTNBhrsd8S7OUexIOE9lJJ0BGZU.png', - u'Cinemax 2': logobase + 'XLVuXXOFFSfsOVKEsFxarjmvLeA5WK.png', + u'Chasse et Peche': logobase + '0X53uW9WfdUDtISMaT71SaPSf0rv3C.png', + u'CNL': logobase + 'wwQ6u4lFXyaUDJLu2JOh6mtbkIz0Nf.png', + u'CNN International': logobase + 'lIwbRbM3ve4ixhFXp75Oap9nzcO71G.png', u'Da Vinci Learning': logobase + 'Yl6p1IDDkZxxiUa3p2JxI66mIlOPns.png', u'Dange TV': logobase + 'ofmgiZlRmwOZtTaYtUtfErZS3ODDDM.png', u'Deluxe Music': logobase + '3VnQoAyJh3RZM88USPszL1TZIE5t0u.png', u'Deutsche Welle': logobase + 'RnqBDfde1HP4OkZhXWlKB1xkHi6Io9.png', - u'Diema Sport BG': logobase + 'AA2hWbb0gyhc7nYtjWXJH9ZrB3D5EM.png', + u'Diema Sport': logobase + 'Gep7g9hp9U5QLU9327KgsNFf9utzNy.png', + u'Diema Sport 2': logobase + '4xUpzBxK0sL6Hc9KQADA1LhmFbE9Yi.png', u'Digi Sport 1': logobase + 'gk0ajzoQjMHlBKM298kWjcfNy1P8ec.png', u'Digi Sport 2': logobase + 'x1HMd7tDoprxuZvq90Uj2Gb1eEOhTm.png', - u'Discovepy Science': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', - u'Discovery Channel HD': logobase + 'SmWnYlOvkJn8GzttT2UY0vmo8PYfMg.png', + u'Digi Sport 3': logobase + 'foaFOGGFFKo1T6HFFkPc8AmaJVgN8j.png', u'Discovery Channel': logobase + 'oKx1ImWVRT3AK3DHYWUVc71JZUkwu5.png', + u'Discovery Channel HD': logobase + 'SmWnYlOvkJn8GzttT2UY0vmo8PYfMg.png', + u'Discovery Science': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', + u'Discovery Science HD': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', u'Disney Channel': logobase + 'JxEjTeXwExjnxutQGKJBmMI85tpNqK.png', + u'Disney Channel (+2)': logobase + 'lPgnmTskftnw81t92AxtZSJJStKJ0K.png', u'Dobro TV': logobase + 'tcHbtjkY9gYD4VqipgwLP2BYxgdrZb.png', - u'Dorcel': logobase + '9bzeA3zNtESvDVsiMyDlcrTAngs2af.png', - u'Dro TV HD': logobase + 'iV5n6aGpDLVpReaJz71u0XrIyAHTcG.png', - u'Dunya TV AZ': logobase + 'C9oEtk1vVd0yApuKTUve53ADQUhNFj.png', + u'Dream TV': logobase + '3k3m3ElnUQ0UhX54qa7hOGbmNjKZSc.png', u'English Club TV': logobase + 'Hf5RUQ91cEGtHoaK3GK6VIZlJ8Leql.png', u'Enter Film': logobase + '8FPA5SCEIj35fBO8yrULnefW4NzIzk.png', - u'EroXXX HD': logobase + 'VxkJjYZQqK3OcevhRKsz2L3OFiaEwt.png', u'Eska Best Music TV HD': logobase + 'wI3e672FQZpD8yr8aIV8Q2fL15zENv.png', u'Eska Music Vox TV OldsCool HD': logobase + '7LW3s6C3D3yeciqTY0CKXpEmL3Tb3w.png', u'Eska Party TV HD': logobase + 'MaX9is65hlDpRDwgOikGE2RqMRxn8G.png', u'Eska Rock TV HD': logobase + 'VU2POxFhYCM4XhFAtOp8Y4sdlbu32M.png', u'Eska TV': logobase + '1KX19DgurgoaSKNTTpjXCeSQr1epwv.png', u'Eska Wawa TV HD': logobase + 'r9d697qox4MFgypnVqeILYWKcfbwNc.png', + u'ESPN US HD': logobase + 'ak7iT40ScLotDyx6iTDcbITwyI0tB8.png', + u'EU Music': logobase + 'soPC6c06dpKRwWZFf1xV7yAUaqpVys.png', + u'Eureka HD': logobase + 'jSMxE4IBjH8pbRJgc2KRwgtq3qWOrj.png', u'EuroNews': logobase + 'Vb3fP5gUK0q40WuzYeUhMT7RQmDg27.png', u'Europa Plus TV': logobase + 'PkatgpdmA4ArsgSG5shu0ZCQQ5RgMx.png', + u'Eurosport 1': logobase + 'QA9jgUaQRrE4vMno04eM3aUrklXOce.png', + u'Eurosport 1 British': logobase + 'sKWbooLWxKfwjge6dMOTWi35gQvcG8.png', + u'Eurosport 1 HD': logobase + 'DpFTzUEA3y67Z6ObTPF4xH0XLNRAZm.png', u'Eurosport 2 British': logobase + 'syqFtqcYUfxX6t2wcfSGKNbKQ7vGgN.png', - u'Eurosport British': logobase + 'sKWbooLWxKfwjge6dMOTWi35gQvcG8.png', - u'Eurosport HD': logobase + 'DpFTzUEA3y67Z6ObTPF4xH0XLNRAZm.png', - u'Eurosport': logobase + 'QA9jgUaQRrE4vMno04eM3aUrklXOce.png', - u'Eurospоrt 2 North-East HD': logobase + '0tv5hm546AKIySs7cpj30LlCOcY0Mj.png', - u'Eurospоrt 2 North-East': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', u'Eurospоrt 2': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', + u'Eurospоrt 2 HD': logobase + '0tv5hm546AKIySs7cpj30LlCOcY0Mj.png', u'Exotica TV': logobase + 'K5hm3mURkkDaSc7RI5RDti5edynMGl.png', u'Extreme Sports': logobase + '21FhIqWK82JDPNuLTEIC9hSO2EHfks.png', u'F+ BG': logobase + 'DJZlMhUwCNcVypOBoytE7QQnKtMepM.png', u'Fashion One HD': logobase + 'iPs2ptiBXm8h0KSnRmyqu45texHNig.png', u'Fashion TV': logobase + 'PSjqabjhYIBqcS8hUA8WNrZEjV4zZY.png', u'Fine Living': logobase + '22I1fK1aMCUgeYUCV0K3vwmlJKELZD.png', - u'Food Network': logobase + 'W68LEGlOOMPtN0qoGXvdkWhHBjKet6.png', - u'Fox Life': logobase + 'rkksGUl3DstQSEyT26Q07hNCEwyNnd.png', - u'Fox Sports 2': logobase + '6huWEAg1M7y9bKk21UkIyJ3yw1RRHO.png', u'Fox': logobase + 'XGC77wQNeEyaJ2z2mDipyIPsoF0xc1.png', + u'Fox Life': logobase + 'rkksGUl3DstQSEyT26Q07hNCEwyNnd.png', u'France 24': logobase + 'pViXgcMjLnB5WyOoQJ7sBxhHo5fTSB.png', + u'French Lover TV': logobase + 'XHZQcVegBeqVMOYDqzr9EruPgbQpX0.png', u'Fоx HD': logobase + 'Pl8S60EJ52htHxi1gAw1SS1y8i1p3z.png', u'Fоx Life HD': logobase + 'Vou521VpOGAGqhp4HUfiG7BSbKNSk6.png', u'Galaxy TV': logobase + 'pU2NsRP9CVtEgQuDTi9jcTYV8iAD4a.png', + u'Game Show': logobase + 'Uc9sBHj0DAYzfXMZmdxeriCBZvUpeb.png', + u'Ginger HD': logobase + 'eW7CqWW2bppbpKzhNctyElyv5Nzs30.png', + u'Glazella 3D': logobase + '7EM3eHww2EvOf9jgeFPdZrsQwO8Fz7.png', + u'Glazella HD': logobase + '7EM3eHww2EvOf9jgeFPdZrsQwO8Fz7.png', u'GlobalStar TV': logobase + 'Y4kNxgbAnPGCt753P73NzCIbcNz5lD.png', u'Gulli': logobase + '2IynBdmw3mmdXt01r8DXKxeor7STnw.png', - u'HD Life (SD)': logobase + 'jUteUS0xRGdvLyBVqNjowEUDkOjT0t.png', + u'HardLife TV': logobase + 'fHPc5oaRdIzKpHTqBFnHA4O2LVf91D.png', + u'HD Fashion': logobase + 'nEIXShrZ56g4IA4VKXReUBF32iYwKJ.png', u'HD Life': logobase + 'jUteUS0xRGdvLyBVqNjowEUDkOjT0t.png', u'HD Media': logobase + 'woAI3zcytfbyiX0LBRToKzErJNy1qF.png', - u'HD Кино 2': logobase + '8zMANTj9xekiWSig7DHFVSb1HlzJdw.png', + u'HD Media 3D': logobase + 'takNty6pirY7TeCRPX5VyUW1mZHHxI.png', u'HD Кино': logobase + 'iHVs7YvUVlUvMTnlma7GMpX5p0Tpy1.png', - u'HD Спорт': logobase + 'dgBOid0Zm2uOU5zvI0ZBAKqUD3n0fE.png', - u'HardLife TV': logobase + 'fHPc5oaRdIzKpHTqBFnHA4O2LVf91D.png', - u'History Channel HD': logobase + 'VFgU260pmiIyPxCzD3f8R7Yc6DXClH.png', u'History Channel': logobase + '9cVifexiWW0qWDhhpnLNVydoZkeRqZ.png', - u'Hot': logobase + 'OjgEHQGo84KMtVReLDbO2zDGF4r2Jt.png', + u'History Channel HD': logobase + 'VFgU260pmiIyPxCzD3f8R7Yc6DXClH.png', + u'Hit TV': logobase + 'FnCnW1vmvm8gjAwMGXe2Q8qtN1C3zy.png', u'Hustler HD': logobase + 'LBz8ia8AASewVuLjMs5v4MDiVYfsJO.png', u'Hustler TV': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', - u'ICG TV': logobase + 'seGXEcBUOeAmwcn2KezmYYuEwTOH8J.png', + u'Hustler TV Europe': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', + u'iConcerts HD': logobase + 'fLNDK6Nxz7xMv61nOsZvED749DlOtz.png', u'ICTV': logobase + 'YuNtYxhj9vqgTU9kVuz9imhqviY4PZ.png', - u'IQ HD': logobase + 'tkCaOFZ7Xf1TmIzhazLXZlcWfJa8Od.png', + u'ID Fashion HD': logobase + 'v5Ahro1G6KQVugydvnyZbHjhM6BxhR.png', + u'Idman TV': logobase + 'UhhUZdtM7FjDFbmoleWalj7tp6nj5o.png', + u'ILand HD': logobase + '3REFaj227EmEOfEbNCXz7eWzle6z1v.png', u'Info TV LT': logobase + 'Sv42hVuB4nb2y04ggrhadg9FYZI3Kw.png', u'InterAz': logobase + 'rdntbmpYEYtyRbW3nGa1FUNZPXF53O.png', u'Inva Media TV': logobase + 'mGWpCUkls40liFDUVrxhaIAFwYXZSt.png', u'Investigation Discovery Europe': logobase + 'QvF9d3DYyndsYsyfjC8aWeSzy6hpns.png', + u'IQ HD': logobase + 'tkCaOFZ7Xf1TmIzhazLXZlcWfJa8Od.png', u'Jahonnamo': logobase + 'OqNuJdvRTdBh5NeydnpmCfMnTJs9XZ.png', u'JimJam': logobase + 'BPDFCK5SQF3mXu5MsDNSdtvz4Gjawo.png', - u'Jurnal TV': logobase + 'qNBTlT1Dtnl0X87LipDLE7uJ0pgLj2.png', + u'Kentron TV International': logobase + 'Zup40Uv14kJO1x3LUTd5OlFzE0uTPj.png', u'Lale': logobase + '99AZ5VwFy9yZrgsdZ6kIxcoYqwSyUH.png', u'Lider TV': logobase + 'LByLhdeQ30Ln5pSZRYSpoLpXQS5Ytr.png', u'Life78': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', - u'LifeNews HD': logobase + 'Mvurp6cp7Sq2fV3tnFBwPtJy7Ifm1i.png', + u'Life78 HD': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', u'LifeNews': logobase + 'K8dBCUNz1BSwbgUYYU04i2qJpQKLMc.png', - u'Liuks': logobase + 'niAMFUABvOhd1M42fmveGQcATd8lK7.png', + u'LifeNews HD': logobase + 'Mvurp6cp7Sq2fV3tnFBwPtJy7Ifm1i.png', + u'Liuks': logobase + 'vUvAku3X6MAJdJWbLC2xbqXnyHEDpR.png', + u'Maxxi-TV': logobase + 'uqyYr07PPk2OuEdnNrbkMHVlGZCJp5.png', + u'Mezzo': logobase + 'GUzJ4cyK50ZahOSIx6tTbwGLJiUnSA.png', + u'Mezzo Live HD': logobase + 'lGOtRiUoGyJUMCtAtUwevBerpQORD4.png', u'MGM HD': logobase + 'o4K0lgc2D1GkuFwMC3Cdg1uK6fMrG4.png', - u'MTV Adria': logobase + 'CiM7BJh1M8SscxNfX5bLhTsizFHubP.png', + u'Mi Lady': logobase + 'Fd556PD2ffD6zdVUiX0dvVfBJavBNd.png', + u'Motors TV HD': logobase + '5t2PirCczEeIqzonCtgbmgpbyOZO5q.png', + u'Movistar F1': logobase + 'no1c6DhrTCZVqnq9N4Z4SZ2dKx3DyU.png', + u'Movistar MotoGP': logobase + 'nvj6jOmluzhKXuUoXWePwrccFE3DO2.png', + u'MTV AM HD': logobase + '4jFcW4ycRkxdcHqHaVM8J2oIxV7re8.png', u'MTV Dance': logobase + 'ZXKNRw6Ai8u4lY0wxeycQwj2dIkm66.png', u'MTV Hits': logobase + 'iLJhuLh9kFLQkG4ERvLGSjSgMfNiM4.png', u'MTV Live HD': logobase + 'WjyYXtYHhG5COxGab7luHb1bmvAioA.png', u'MTV Rocks': logobase + 'SEwn7rL2FxPcf5Ol9KRKedIwXlpsAP.png', u'MTV Россия': logobase + 'P5ijp2sRQsKVZkOJwjdqIVgYdJzhpZ.png', - u'Maxxi-TV': logobase + 'uqyYr07PPk2OuEdnNrbkMHVlGZCJp5.png', - u'Mezzo Live HD': logobase + 'lGOtRiUoGyJUMCtAtUwevBerpQORD4.png', - u'Mi Lady': logobase + 'Fd556PD2ffD6zdVUiX0dvVfBJavBNd.png', - u'Motors TV': logobase + '5t2PirCczEeIqzonCtgbmgpbyOZO5q.png', - u'Motorsport': logobase + '7fAmhbXY4MwyRxACHpWPIaNqNiW6Y8.png', - u'Movistar F1': logobase + 'no1c6DhrTCZVqnq9N4Z4SZ2dKx3DyU.png', - u'Movistar MotoGP': logobase + 'nvj6jOmluzhKXuUoXWePwrccFE3DO2.png', u'Music Box RU': logobase + 'zaHCW7nPCyGRnqHDkenCIXo7d6vR7v.png', u'Music Box TV': logobase + 'fvt4pris0lwnVhSyUrh8QlyzWBhbgz.png', u'Music Box UA': logobase + '8T7Rnct8q2VHhRm0BcGFxCjxuYEArc.png', u'Music Box UK': logobase + 'KUbeGDe2HthXSDa8OqMhia0nhTSL61.png', - u'Music InTV': logobase + 'QTJnJokXeUpNksMV5cp0TiE9lyUesQ.png', - u'NHK World TV': logobase + 'JJ8Sh9c7zA3PaXlK1ZaNjy3GgWQFh2.png', - u'Nat Geo Wild HD': logobase + 'YYa1wyNA9prFK1APZ2ZSHGirPpm8kY.png', + u'Mute TV 3D': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', + u'Mute TV HD': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', + u'MyZen TV HD': logobase + 'Nm18g9xkyhx73F2vxLCPYVRwX6k0H6.png', u'Nat Geo Wild': logobase + 'ciHIDUuHEnkEuPghbcqkDQx4vadle3.png', - u'National Geographic HD': logobase + 'hK1waimMq9eAp0ugM19moSoQvUeve5.png', + u'Nat Geo Wild HD': logobase + 'YYa1wyNA9prFK1APZ2ZSHGirPpm8kY.png', u'National Geographic': logobase + 'i6STSw6Hg1wWP18yBAOyKoKpSMeKLu.png', - u'Nautical Channel': logobase + 't3fIrxEyf2vpizbuznXFKet6yOpEfh.png', - u'News One': logobase + 'WmOw8mtP3YhXptHbvayITx0TNJdB5Z.png', + u'National Geographic HD': logobase + 'hK1waimMq9eAp0ugM19moSoQvUeve5.png', + u'News One': logobase + 'SnaQqBa1YMS2b8b8EGdZSMCConLbDS.png', + u'NFL Network HD': logobase + 'Mr01K0LgGHKs3Zd1fnvMTuBSFyvu40.png', + u'NHK World TV': logobase + 'JJ8Sh9c7zA3PaXlK1ZaNjy3GgWQFh2.png', u'Nick Jr.': logobase + 'D87kJ3fIWIm5wKi5qxm24nbuPQv0U8.png', - u'Nickelodeon HD': logobase + 'CFYx7Bd1aDSgkqjMLLQjFE6xe3u1E0.png', u'Nickelodeon': logobase + 'j66xpaZbfiYIgQxv76QAPckPVjmLNs.png', - u'Nova Cinema': logobase + 'cgxxcQ2S8E2XMoSMlF5pLdQ9ySJD0e.png', - u'Nova Cz': logobase + 'Z3wXiyZqhbm9uRZDAJcVuaQjoESQtr.png', - u'Nova Sport BG': logobase + 'BZNQubMesUUPrPyQdhdUEICSjkoaXf.png', + u'Nickelodeon HD': logobase + 'CFYx7Bd1aDSgkqjMLLQjFE6xe3u1E0.png', u'Nova Sport': logobase + '8npwMe6SAEgj5nMBCoVPCemX9fKOvI.png', + u'Nova Sport BG': logobase + 'BZNQubMesUUPrPyQdhdUEICSjkoaXf.png', u'Nuart TV': logobase + 'aqMIuUixqLQYmJITPnOGtFkRPuTqKa.png', - u'O-TV': logobase + 'UlIt4rE3FZs7IQmPN3tsGS6fqY5F1D.png', u'O-la-la': logobase + '7ddvb8Tivq7yuEgffrKildHi2BQcCE.png', - u'ORF Sport +': logobase + 'JQlgjMTC1udkBdrHMiKd2UIV2sJlvw.png', + u'O-TV': logobase + 'UlIt4rE3FZs7IQmPN3tsGS6fqY5F1D.png', u'Ocean-TV': logobase + 'cvBAngU16nJU1bEzxAEcMPiPvf7fVT.png', u'Outdoor Channel': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', - u'Paramount Comedy HD': logobase + 'AvH8i4NV780Q7PcHi5KkxnCDU0Y0yl.png', + u'Outdoor Channel HD': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', + u'Paramount Channel': logobase + 'Iyzjhb5BBRKfvEZdBwNJ785jzroODU.png', + u'Paramount Channel HD': logobase + 'Iyzjhb5BBRKfvEZdBwNJ785jzroODU.png', u'Paramount Comedy': logobase + '5EtvAWXB7VK1Yw82yvO28sY28dU4ZC.png', + u'Paramount Comedy HD': logobase + 'AvH8i4NV780Q7PcHi5KkxnCDU0Y0yl.png', + u'PassionXXX': logobase + 'U2oO3j1eda31nrTwHqaQ8psrLfH5CD.png', u'Pingviniukas LT': logobase + '8X45gGlJq2Vx2jUts3EhYESzpwjmPo.png', u'Pink O TV': logobase + 'aI99kH6bHY5Qt2ph4W1Nh0wxdzd1p8.png', u'Playboy TV': logobase + 'lIWBxmt5GDl9tg4KsQNtA0CuZWdOHH.png', + u'Poker Central HD': logobase + 'XfvAn9FaPBiRrhXBluK2kUPqxSftAo.png', u'Polonia 1': logobase + '3fzUyCBgUoJ6zqc92KSXh9g9utJEuO.png', u'Polsat Sport Extra HD': logobase + 'AdIBJziuh63ffasA6KeDMAKV9DPBqL.png', - u'Polsat Sport HD': logobase + 'zAC1zjpeHrGa3vb5nPLvvNJ8SooQes.png', - u'Polsat Sport News': logobase + 'WhGmGGlQXEvepTfPBd3u1to4ekbtDN.png', u'Premier Sports': logobase + 'dIKsh48t49lFlDcKPhHKsvYsKaOfNS.png', - u'Pro Все': logobase + 'F8P8nU4YKStbVP2CQD1iaTkcDBzaJn.png', - u'QTV': logobase + 'PahwxLR2J1qfVSGGr54hdPeUD5vpsy.png', + u'Private TV': logobase + 'fAju50cZ4EUz2friirWrJgWfkY4R7s.png', + u'Pro Все': logobase + 'OavKhOpBMn27qPDKJXXm5rgATgovgn.png', + u'QTV': logobase + 'zmh1xStjBZGJ6U5g3xLoemD2oT3w3h.png', + u'R1': logobase + 'OducDuiwKukJTHXoQo49vuh62PvXK2.png', + u'Real Madrid TV': logobase + 'KfoLG7goywcRhMMCc3sO8IVwhuATLp.png', + u'Retro Music TV': logobase + 'zVS9G1oine56udlAK30gNut16iJ9Ft.png', u'RMC TV': logobase + '3CiAgachWg7ohgoU1Gilcm73hXhT41.png', u'RTVi': logobase + 'QBnba0xrWtpPWLL4yKDRixaCRQAmaP.png', u'RU TV': logobase + '161.png', - u'Redlight HD': logobase + '5tFZcJtKAZRbXtKGDuLe8FZ3lK9LI5.png', - u'Retro Music TV': logobase + 'zVS9G1oine56udlAK30gNut16iJ9Ft.png', - u'Ru Music': logobase + 'sAVGvLHkObOsYWh36zLIDjxTCWANlo.png', u'Rusong TV': logobase + '186WxZMn3PGQyMlWsItM9JkSS4Tt29.png', + u'Russia Today': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', u'Russia Today Arabic': logobase + 'OPbYfpQc4ShP8JmgPeiavUroY98H8L.png', + u'Russia Today Doc': logobase + 'VOYx5PIhDPrWiZIcCYpm1xrDSQZnsN.png', u'Russia Today Doc HD': logobase + 'b8QePfFi6zsCDS7hfTeFWES5UN4SAk.png', - u'Russia Today Doc Rus': logobase + 'VOYx5PIhDPrWiZIcCYpm1xrDSQZnsN.png', u'Russia Today Espanol': logobase + 'zkPwjfFbktNO8EYDaHlYhwAeT88dmY.png', u'Russia Today HD': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', - u'Russia Today': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', - u'Russian Travel Guide HD': logobase + 'g4MDyw0yqXWkIar8eH0cgCz4xhiKON.png', u'Russian Travel Guide': logobase + 'IeaOjwR6Q9eJjGZr0LYk2tpchM3ITZ.png', - u'SET HD': logobase + 'sX1unYoKj8JR7m8lbtkmPCClRrjAZ9.png', - u'SET': logobase + 'tKzOKIcOQYrFl1VL8B0QqERFXCAYfU.png', - u'STV': logobase + 'DbKEKL5gUOFHiruYRjY2H9gTLOV5mu.png', + u'Russian Travel Guide HD': logobase + 'g4MDyw0yqXWkIar8eH0cgCz4xhiKON.png', u'Satisfaction HD': logobase + 'isdNgbfGENuaDPSMzsz8WMjBzc1rah.png', + u'SCT': logobase + 'bDRN0guXHaNHruxtyFkpKYz8J09Xj3.png', + u'SET': logobase + 'tKzOKIcOQYrFl1VL8B0QqERFXCAYfU.png', + u'SET HD': logobase + 'sX1unYoKj8JR7m8lbtkmPCClRrjAZ9.png', u'Setanta Sports + HD': logobase + 'jW7pJhmebW2fZsXUTvDuRQLahgiLlU.png', + u'Setanta Sports Eurasia': logobase + 'RMqd8QOE9eqzAv7pDstbAJlSAXL3RN.png', u'Setanta Sports Eurasia HD': logobase + '1wgHdJP76TCItF14FxDwBtak8tmxRv.png', u'Sextosenso': logobase + 'HXMvFMLO9weHUcolVmmMKpz98T0K4K.png', u'Shant TV': logobase + 'IgJpH1JzyjI55Ki2G2Ybz6jzOkwG0T.png', u'Shop 24': logobase + 'bCuxLyvoTk8l5cBRSyKFXjWjYucvlu.png', + u'Shopping Live': logobase + 'HVwxC489SYFr8Ttqs1he9RZjEmJjPn.png', u'Sky Sports 1': logobase + 'pzVzAnJJ90768a73nJXWFAni9yYPT3.png', + u'Sky Sports 2': logobase + 'iCLUex5RU5u1AbCkyiDJVfZgX1Y5BP.png', u'Sky Sports 3': logobase + 'UAUhQNqNqyEgXdUoeffjssUZ262Lsx.png', u'Sky Sports 4': logobase + 'qqJzEqYys67VAtSt59keaQaJdM4LUg.png', - u'Sky Sports 5': logobase + 'yFt8HuKsDvuLii304FlhbEIPZ3gTvi.png', u'Sky Sports F1': logobase + 'J9T7KUE84YobHuNThXN6Hl9UG21tD9.png', - u'Slagr TV': logobase + 'MGCm4Mz8Ggf7xrG9CpfB5QUUOcmfEq.png', u'Sony Sci-Fi': logobase + 'zLWEgf9BbxBr1TR2Qj2NxVQBuPecEP.png', u'Sony Turbo': logobase + 'CaPjVaQrpyN138TarQ7CYBqBOz0ZF7.png', u'Sport 1 Cz': logobase + 'kCLlfkFz3Ba3BL9Jc9ZPgUKXh2piyv.png', u'Sport 1 LT': logobase + 'nRFc87aOV1vRnjEqmQZUneZe4HiCqn.png', - u'Sport 1 Select': logobase + '899pteevSriMFxe4omDA4G6l9i0czY.png', - u'Sport 1 Voetbal HD': logobase + '0WEqpl3cqObcLs2J0l5DDhVPZalvXx.png', u'Sport 2 Cz': logobase + 'YLmEjnczWQGJcZC0SxRcH4ifPcwYlx.png', - u'Sport Klub 1 HD': logobase + 'cLQ3uuWhQqxCQk5RUDwA9x7bLUHBwn.png', - u'Sport Klub 2 HD': logobase + 'LiIua5Nyy8xdHFYhwgrwbcajbKz6fH.png', - u'Sport Klub 3 HD': logobase + 'UkUGpf3hamDPGPtkqximS96rrts4jx.png', - u'Sport TV 1': logobase + '7u5sbYjzJdQopdQ6bAH7GLDUsPWnXc.png', u'Sport TV 2': logobase + 'u6T8L5PPYKHCbBATjdzjLpTC8zzCdV.png', u'Sport TV 3': logobase + 'dYTM6Oqhaqw18FI6uYPS5yhjCmc1nZ.png', - u'Sport TV 4': logobase + 'YfFhr0OCmbN8vHUuGCLp488dxGpKVw.png', - u'Sport TV 5': logobase + 'YyzfJTMsBcmTKptLzxZcAcLKFj52LT.png', + u'Sporta Centrs TV': logobase + 'FJky9xgBrm265yuoNH3K2OZTINMLPg.png', + u'STV': logobase + 'DbKEKL5gUOFHiruYRjY2H9gTLOV5mu.png', + u'Super One HD (SD)': logobase + 'eSbbtO5TzmQ2gDBpeGsKrMG8bKbiG1.png', u'Super Tennis HD': logobase + 'mjQW91VJdjIEhADvOO2s6OiKNeUdUK.png', + u'Teledeporte': logobase + '57r0Kq1rFB6vcMeldfWDvp438Jz5qT.png', + u'Teletravel HD': logobase + '4ZlASq3oDpOjXfhwluOzY74sy9elaE.png', + u'Ten HD': logobase + '2zGLzb2KRlh0RVlCReqyNantzttGGO.png', u'TGirls TV': logobase + 'FufZ2heFswzvAbRkTQZs8UJBYGsxuG.png', - u'TLC HD': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', + u'TiJi': logobase + 'mD3GW0E7rdPwc4stjk7xrLI2gZn4Hq.png', u'TLC': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', + u'TLC HD': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', + u'Tonis': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', + u'Top Shop': logobase + 'uD257Lhw7Ko2YD1reC0nRqW7lpy93D.png', + u'Topsong TV': logobase + 'DsJRpcbI6rgjONbQftC5nHt1XMAXYQ.png', + u'Travel + adventure': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', + u'Travel + adventure HD': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', + u'Travel Channel': logobase + 'fhmrYjlpC0YMxFd2RqOolbjlXMr0tI.png', + u'Travel Channel HD': logobase + 'zfnAGLCvIu1fx9hfrITAZMoo9HYww4.png', + u'TSN 1': logobase + 'GJsJeixsOKvFaXz8XKqvX4Uh6sUicu.png', + u'TV 1000': logobase + 'WJMEvVafVakrm7BUMy1lzku7VQCx25.png', u'TV 1000 Action East': logobase + 'GblbxkDGXZyW5oWt9W8wuERQAiZ7ZT.png', u'TV 1000 Русское кино': logobase + 'ch5DX6f8hxDnmyzrjotUoKHNGzcw9P.png', - u'TV 1000': logobase + 'WJMEvVafVakrm7BUMy1lzku7VQCx25.png', u'TV Bakhoristan': logobase + 'LoXaN929SQC5r5aQ3JETDXwG6VlPMk.png', - u'TV Plus BG': logobase + 'cxxTbCRSZsh4l1CNpZ4mYychepxUGw.png', u'TV Safina': logobase + 'mJUmNhJbQqcr2NPppAryEJqDPBJGV0.png', u'TV Sale': logobase + 'hs0YdiUTlpRtb3wTiP4cXboX0H9oTN.png', - u'TV Smichov': logobase + 'hqgkCNoqMXiAgNU6uedqUNIR7Z0ox5.png', u'TV XXI (TV21)': logobase + 'TKchoTWZFRMmGDBok08zoEFJ8mJJCe.png', + u'TV1 LT': logobase + 'CXD4Zq54oHMsByuJbWtdIqzD24dVVo.png', u'TV1000 Comedy HD': logobase + 'ygGiR2hkQLySH6khdo8GV9CyMJ8dXi.png', u'TV1000 Megahit HD': logobase + 'lVPY7WCjn1WM6NL6tfLFy8iGA4yk3Z.png', u'TV1000 Premium HD': logobase + 'raoDrpin8VKmi522LZWzSF0fLRO04m.png', u'TV2 Sport HD': logobase + 'iL3TM972YPxOxajyfbuNcKGPFrVvTg.png', - u'TV4 Sport HD': logobase + 'm8tNJfJGN7cYZtUWBggz3PMVB28clK.png', u'TV5 Monde Europe': logobase + 'ko7rbRBnyK1iINkLOA2adRvgVOEgUK.png', u'TV6 LT': logobase + 'SKskx67yBUvbTdMIIZjH1Z4EcB8nYX.png', + u'TV8 LT': logobase + 'X4DIGhjllSDaaXoZ98BrpkFQ2hHfB6.png', u'TVT 1': logobase + 'CKKdhDfmno9O52tMfWptiAQT0IBWV8.png', - u'Teledeporte': logobase + '57r0Kq1rFB6vcMeldfWDvp438Jz5qT.png', - u'Teletravel HD': logobase + '4ZlASq3oDpOjXfhwluOzY74sy9elaE.png', - u'TiJi': logobase + 'mD3GW0E7rdPwc4stjk7xrLI2gZn4Hq.png', - u'Tonis': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', - u'Top Shop': logobase + 'uD257Lhw7Ko2YD1reC0nRqW7lpy93D.png', - u'Topsong TV': logobase + 'DsJRpcbI6rgjONbQftC5nHt1XMAXYQ.png', - u'Torrent TV - Android': logobase + 'wf43FCQBGnvSrknDmSJXTOtbVWgOiP.png', - u'Travel + adventure HD': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', - u'Travel + adventure': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', - u'Travel Channel HD': logobase + 'zfnAGLCvIu1fx9hfrITAZMoo9HYww4.png', + u'UA:Перший (Украина)': logobase + 'iN5vheceBnPmO18uXHGgKwq7q2PHtF.png', u'UBR': logobase + 'F6EzmjkOBVB0gmn1kQX6itv5VvFml5.png', u'Ukraine Today': logobase + '3AVq6O577A7uw9uZ7fxIvpvE3CxdtW.png', - u'VH1 Classic': logobase + 'FhxUFQ2Bsfom4vb8Ce41gFObAbh1Vh.png', u'VH1': logobase + '58.png', - u'VIVA DE': logobase + 'HagNMshKtJ7zKnk9fdmBLhITjoWdrJ.png', - u'Venus': logobase + 'R2ug0cuB3SmBBA6LK1uoNbEV66u39v.png', + u'VH1 Classic': logobase + 'FhxUFQ2Bsfom4vb8Ce41gFObAbh1Vh.png', u'Viasat Explore': logobase + 'uCqpsdKP0ialUUYxUk2fXshYdYfxzW.png', u'Viasat Fotboll': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', + u'Viasat Fotboll HD': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', u'Viasat History': logobase + 'MWGbB8wJp5Gm4vbPHl0ktohDDjMKdr.png', u'Viasat Hockey': logobase + 'CuAbCRGdf3Z1FGFiwErTbHZ3lAMJzr.png', - u'Viasat Motor': logobase + 'RuYtGxEpqJ5DG7WxGCMWNDXosRdh59.png', u'Viasat Nature East': logobase + 'yimDcPvajJcUKQm9bY15cDdp3rJFcp.png', u'Viasat Nature-History HD': logobase + 'pSP6zxmuO4PU6xa6KRlZ9L8vvVM2Dy.png', + u'Viasat Sport': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat Sport Baltic': logobase + 'ZIITckvF1w5u1MlubmhoG45HxPgcZZ.png', u'Viasat Sport HD': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat Sport Sverige': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', - u'Viasat Sport': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat TV3 Sport 1': logobase + 'LUsZ9yjy6izQJHd2z2Hf7uBZ4UyUcM.png', - u'Vip TV HD': logobase + 'VXNvw8nbJhjRncTmxkuglf8htUxN2N.png', - u'WedTV (Свадебный)': logobase + 'u93ysJkZEp1NzeG7jTbVgB7nKhDTqH.png', + u'Viasat TV3 Sport 2 DN': logobase + 'bIcffZkSHB46rCxbvKYcIl1OSfrcDf.png', + u'World Business Channel HD': logobase + 'fMdJvc8moH4HGH5OYqsZwskCowQYuE.png', u'World Fashion': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', - u'WowGirls': logobase + 'phiImbBi8hRs3LqmOOpLVsPqQkD2Hc.png', + u'World Fashion HD': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', u'XXL': logobase + '6nJtj85PlL0MxB8RDkM3toyGND3Anc.png', u'ZDF': logobase + '5SH5FeZiITw27CPxscjksZp272u7He.png', u'Zee TV': logobase + '1HooaeEhMSvpKmWv6nneZxnTmG5r6Q.png', - u'Zoom': logobase + 'SyisYhg411o7z9kXci4vfpLq4KBZZ4.png', - u'bTV BG': logobase + 'xiNqovHjloSoVzrVieKo6saLQTUnJ7.png', - u'nSport + HD': logobase + 'JSpj8Lq758dRJzBaTEjM8nbSfnLf9M.png', - u'АРМ ТВ': logobase + 'OgrdBlfYISfcpr0XO0ImEyelCMjUVx.png', + u'Авто 24': logobase + 'GZwKgvkC0vfYquFAn1dKhppNxdDit9.png', + u'Авто 24 HD': logobase + 'GZwKgvkC0vfYquFAn1dKhppNxdDit9.png', u'Авто плюс': logobase + 'WkRxjy6fJEBJ5NZiaGn2j05eqfFfQq.png', + u'Беларусь 1': logobase + 'v5gOoahV1MZrNsZz6pCp335pW8uYHf.png', u'Беларусь 24': logobase + 'GxA1KJP5YwpWc38BoPEmLwQH6uDeEz.png', u'Беларусь 5': logobase + 'aMU4HXJN11Bo9WissbPW4rhe06vAql.png', u'Белсат ТВ': logobase + '9VYuUQxx1ss7ieu2upENtlibyamBP0.png', u'Бигуди': logobase + 'JvcMdB5e6KVBpbXT12ulzmDqenheRx.png', u'Бобер': logobase + '2Edln8vEbg7UUSVUo7lIJPR780OWAR.png', - u'Боец': logobase + 'pmkJgRqsuZDzuN4c6v6jZaBVKCN3K3.png', - u'Бойцовский клуб': logobase + 'oo4RN3hUUjuVbtegW8Q5QE0bT6GwwD.png', - u'ВТВ': logobase + 'svsUD6TinXyv3B1q5sZf3fI9ebmpaF.png', + u'БСТ': logobase + '05dZ8fzOmf1lXYue2OvVsQ21eIeX69.png', u'Вместе РФ': logobase + 'qa50GYekwBWym7KtoJdzrWHWqN8TeU.png', u'Вопросы и ответы': logobase + 'xbV8M35FkvpieQ3TUEL8fhwU8MzjmQ.png', u'Время': logobase + 'F44yKDJQLsX0llpZ2wupg8V5vHx5fF.png', + u'ВТВ': logobase + 'svsUD6TinXyv3B1q5sZf3fI9ebmpaF.png', u'Громадське ТБ HD': logobase + 'Ovkd9TiVv3nLcKPwQS2wkJ85KyYCMQ.png', - u'Детский мир': logobase + '00Vf3rPABNnbNQ6Rv0dnfcg3JsJelA.png', u'Детский': logobase + 'jk8kody2p38CKdj5KGXWMwRLjgFIlG.png', - u'Джус ТВ': logobase + 'qVNFoyUAOJSDvN9tHhf9j2AP7x4VkV.png', - u'Дождь HD': logobase + '381.png', + u'Детский мир': logobase + '00Vf3rPABNnbNQ6Rv0dnfcg3JsJelA.png', u'Дождь': logobase + '381.png', - u'Дом кино International': logobase + '69MqZE2YHJNewQkqRbJea33WuRkKgo.png', + u'Дождь HD': logobase + '381.png', u'Дом кино': logobase + 'jlC78Fy13KWjQUN6l3FtbsRLZDvc0x.png', - u'Домашний (+4)': logobase + 'LRMaRyPCroUq4dVcRhwKJKVuhvdvUZ.png', + u'Дом кино International': logobase + '69MqZE2YHJNewQkqRbJea33WuRkKgo.png', + u'Дом кино Премиум': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', + u'Дом кино Премиум HD': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', + u'Домашние животные': logobase + 'HiWAmn5RvUKNJnSW2Jhxjs6maoNFV7.png', u'Домашний': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', + u'Домашний (+2)': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', + u'Домашний (+4)': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', + u'Донбас': logobase + 'vsj1IA3Z8QVL3AzzuN4EshgT4LUmRr.png', u'Драйв ТВ': logobase + 'pmmgMKcRbxeYkjVUVr4IAWM0UuZHO4.png', u'Еврокино': logobase + '34mszCG0j0Vf6kFcMrLPnFEA8UPdu6.png', u'Еда HD': logobase + 'ojUD1jhpv7HBOLmubpEBOsANkpYNtk.png', - u'Еда ТВ': logobase + 'TWdAdMXfMSylb2mQ4efFnOAYosymNC.png', u'Еспресо ТВ': logobase + 'lOwm890F5URuR5Ej7IacerzECPIDt4.png', u'Живая Планета': logobase + 'xgKSMwqBdEyXnbVgb8LtNXSMiaPcOx.png', u'Живи': logobase + 'cOluSjslxxs3JZtSVO8c15xh7h8SDU.png', u'Загородная жизнь': logobase + 'cGGo8HRkVhy66UXKXZ4tH5HyUaaxJA.png', - u'Звезда (+4)': logobase + '01VqCLfVy5OsMBN1qXjxOTp5NKT4QS.png', + u'Загородный': logobase + 'RX345W0BBqJbR3XdROHMi4dnbwqwlt.png', u'Звезда': logobase + '0HLRrFHt2QIkbJpLc1fy0RVe7hqCEC.png', + u'Звезда (+3)': logobase + 'IVxzhUbLe9DUhOtnjpDYpITMrBdybS.png', u'Здоровое ТВ': logobase + '2LgJcyMnjJpMAhUqX3rdQ4ChOmbuTo.png', u'Зоо ТВ': logobase + 'RtAhntWPlKQs6CIYAb72piNF9EsN3E.png', u'Зоопарк': logobase + '1Ugpb5T1THFcFpn19Mnua21KxHkjct.png', - u'Иллюзион+': logobase + 'XOO3bLrAAvCj45nIsxsGCppY14bY1n.png', + u'Иллюзион+': logobase + '8LToTNvWRBHvb5IKoteKm8EwAGw8mv.png', u'Индия': logobase + 'XVWyHt5bFFcZNzmysBSjuVdGBGl45D.png', u'Интер': logobase + '3SP67FapzyZqMVZTPiJIcN09KRkTeu.png', u'Интер+': logobase + 'QEdaDBbqr13CCfwKQAP77UZYPQIPn0.png', - u'Искушение': logobase + 'p3WsIen84SZK76zTMWnslNgUjsqsMZ.png', + u'Интерра ТВ': logobase + 'Akn9ntxqkSNrX3BTFRRtXPaEYBszjR.png', + u'Искушение': logobase + 'OQF9mLHIZ87QCphtuNTx6mLJNn5Czf.png', u'История': logobase + 'PNRaeOUFzOPFtrclFBBRTckj6Lvo0u.png', u'К1': logobase + 'mk2mYb28HFIxkFIiMNQWmKUdn1Y8hD.png', u'К2': logobase + 'IjG76jf8k8HTNLooNpUiEXtkPfA2rG.png', - u'КХЛ HD': logobase + 'kRN7BwVtcdaXrU4Mdg24qhFAxjx9oZ.png', - u'КХЛ ТВ': logobase + '216.png', u'Карусель': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', + u'Карусель International': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'Киевская Русь': logobase + 'C1AZimW2NnNA17H1uJLxxePUMTPQZ7.png', u'Кино ТВ': logobase + 'KkITMDICqC1erWdSqyOqoccqde2wHC.png', - u'Кино премиум HD': logobase + 'p580CRZ8bBS6dw3plMWhhxXSzQ59uS.png', u'Кинопоказ': logobase + 'v0JEbxExcFI8dVEzCkpZUoktgiS9t7.png', - u'Кинорейс 1': logobase + 'q3N266MTLCzzNVXy3q330VIVgTp93L.png', - u'Кинорейс 2': logobase + 'RO9ac4e18hSAsPhquZ9JzyTHo5oqMK.png', + u'Кинопоказ 1 HD': logobase + 'pNA1vR3sPoovYNKzO4TU6NQcSXNqjk.png', + u'Кинопоказ 2 HD': logobase + 'Yf2XKrtorOF2wSZ23Q9NHhL2MfOcqi.png', + u'Кинопремиум HD': logobase + 'X7n162e9CVIachbSlwXU2qtA1C7zz7.png', u'Комедия ТВ': logobase + 'L2MEpT2YePoDvmKRjYy6yyt5ssH1m4.png', u'Красная линия': logobase + 'I43S6jd5noclar0LlPJnyY8adonmUV.png', u'Кто есть кто': logobase + 'MwNkO3fXd6KefRdiGlOdOQ5q0Zu7kS.png', - u'Кубань 24 Орбита': logobase + 'FauvJxsKmI5a1fR62uSH9hJfHs5TCr.png', u'Кубань 24': logobase + 'CAAqiN96tQzFDdtz3vjrrgeIjAKqNq.png', + u'Кубань 24 Орбита': logobase + 'FauvJxsKmI5a1fR62uSH9hJfHs5TCr.png', u'Культура Украина': logobase + 'pyKdve4YhoChQFGSha8J0FBWBf302a.png', u'Кухня ТВ': logobase + 'G0WbVMphlP9oJ6KvHRfx0xDfhrF9Re.png', + u'КХЛ HD': logobase + 'kRN7BwVtcdaXrU4Mdg24qhFAxjx9oZ.png', + u'КХЛ ТВ': logobase + '216.png', + u'Любимое ТВ': logobase + 'qVREQyj0rN87jDZ1ylYW0sDvzfNp8p.png', u'Ля-минор': logobase + '8FJA3xMMHcrZuGifHViyVQLjVIem5u.png', u'М1': logobase + 'ezvu2ugYMGnZ968LlnjPw7VjqWIPeM.png', u'М2': logobase + 'U4s78hznNz7mFYZQICkxN7J0HTtlCP.png', u'Малятко ТВ': logobase + 'kjYF9vS2IDTMehpzC7WWfjnZ4NVpuk.png', u'Мама': logobase + 'nw9fROQIjjKSDp8Wjkjl1Wt0n0xHxd.png', - u'Марс ТВ': logobase + 'KnDO2ZAW1Xlahhp1ysdlDUPCQI3Jix.png', - u'Матч ТВ HD': logobase + 'MXyy9Uud7oDuH8JqVisjsD0csgAHnQ.png', u'Матч ТВ': logobase + 'hQDOuQjUVczvUU2ocLE0tkC1siCqpo.png', + u'Матч ТВ HD': logobase + 'MXyy9Uud7oDuH8JqVisjsD0csgAHnQ.png', + u'Матч! Арена': logobase + 'DDjh1dM2D09Wcl3L6YmEd1si3P17n0.png', + u'Матч! Арена HD': logobase + 'DDjh1dM2D09Wcl3L6YmEd1si3P17n0.png', + u'Матч! Боец': logobase + 'xj1tPp6g9LmQH5KAN0gFpWYoa9RdbL.png', + u'Матч! Игра': logobase + 'urIB7TbFWo36EmXW9eG3w4qre7MdX9.png', + u'Матч! Игра HD': logobase + 'urIB7TbFWo36EmXW9eG3w4qre7MdX9.png', + u'Матч! Наш Спорт': logobase + 'l0x8GopxrHL6jLiDGVHi8tIEc7RBt2.png', + u'Матч! Планета': logobase + 'mWQ92sSmszzmjme9GTueLPCxagJGtl.png', + u'Матч! Футбол 1': logobase + '9PM8M6cN21wQ3M5isVZgjNepzUI4Ry.png', + u'Матч! Футбол 1 HD': logobase + '9PM8M6cN21wQ3M5isVZgjNepzUI4Ry.png', + u'Матч! Футбол 2': logobase + '8MA3WloO6RsWX8N7Ck5ugek2Kirf4B.png', + u'Матч! Футбол 2 HD': logobase + '8MA3WloO6RsWX8N7Ck5ugek2Kirf4B.png', + u'Матч! Футбол 3': logobase + 'OLHdmyfUev4mMX0OGniJrlUwHnMKOg.png', + u'Матч! Футбол 3 HD': logobase + 'OLHdmyfUev4mMX0OGniJrlUwHnMKOg.png', u'Мега': logobase + 'IXY7dRFoq0qCqn4UbY47iP36vVZ6ck.png', + u'Мир': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', u'Мир (+3)': logobase + 'QxOYkz6f80IdhmC4RSHI1cMd32CqYZ.png', u'Мир 24': logobase + 'auv6717gJOWi0A2VoeDQaCsx9G1NOj.png', u'Мир HD': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', - u'Мир': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', u'Многосерийное ТВ': logobase + '4TMYdVpZYXafyIumuB5d7PrjFnslyT.png', + u'Морской': logobase + 'uzlb3awoyNqvIcf6i35hTVqf7gvuqz.png', u'Москва 24': logobase + 'dZcmoqRoZLhCBh8BE4RnbQivuDY6hH.png', u'Москва Доверие': logobase + '9oPazhJQrGZcSN64ZOS3WjLwGmQIZy.png', u'Моя планета': logobase + 'Qa41eifERrD77xQsmpRGbeTq95Ldlv.png', @@ -409,91 +443,89 @@ u'Музыка Первого': logobase + 'fD2Hnsq5BPMGvobLDMPZP049yNhBYt.png', u'Мульт': logobase + 'ZVzHvGF8mZ6RTsSh6aWsPbF1FBLjyp.png', u'Мультимания': logobase + '132.png', - u'НЛО ТВ': logobase + '2VGhYruaQo19G1NLGoOiTrwmPxef7d.png', - u'НСТ': logobase + 'fKYzdlWRz68qd9mRZnWuxMY73EyaSz.png', - u'НТВ (+4)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', - u'НТВ (+7)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', - u'НТВ HD': logobase + 'zdJ3ye6d3UWl5a56zm6LjqYH6ziSOs.png', - u'НТВ': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', - u'НТВ+ 3D': logobase + 'qvuG0JySlHPlEH9A7G4xMNjBqOB35h.png', - u'НТВ+ Баскетбол': logobase + 'bIWuyv7DJ65D5hIANkeo9SyIHGUXtn.png', - u'НТВ+ Кино Союз': logobase + 'F3RiiQtowA2YoHw73iEzcMLZwfDiSx.png', - u'НТВ+ Кино плюс': logobase + 'cnz8ZMypP2HV6phwv3rkSVQ7CgJExi.png', - u'НТВ+ Киноклуб': logobase + 'nRnksgRuhojvbFDqh5KZ30XJQ4iyFO.png', - u'НТВ+ Наше кино': logobase + 'UXJcZjVdZVIzciVHgGT3e6XxdRaBsD.png', - u'НТВ+ Премьера': logobase + 'lDiI54Y3LjIAOg5VV0adicP3OJrdgo.png', - u'НТВ+ Спорт плюс': logobase + '222.png', - u'НТВ+ Спорт': logobase + '2WCUNhvAYk7RJUCcbt4N8xvOxWGlbx.png', - u'НТВ+ Теннис': logobase + 'SdtlGA6I7WvjOpHsbabE4C9DP7JvJ7.png', - u'НТВ+ Футбол 1 HD': logobase + '5gVddUBrGBIvdTx0cpRgCMJwVgphJz.png', - u'НТВ+ Футбол 1': logobase + 'EQQJV8zgnv5MCfa5VBcvOm1GsLWovM.png', - u'НТВ+ Футбол 2 HD': logobase + '8X1dxETwOup3Qton0J35BoW5glu5UG.png', - u'НТВ+ Футбол 2': logobase + 'hD6OLNWbxyDtqE5VlVxCaoNeEoYpFb.png', - u'НТВ+ Футбол 3 HD': logobase + 'eC4IeAxFTXXMsVfQaQHPtAN7LvosGd.png', - u'НТВ+ Футбол 3': logobase + '4B2emgwWQ7kgFJwdoh0zNxDguh4Fh3.png', - u'НТН (Украина)': logobase + 'LpQE1Odb1EoH5dJ90gWjItVyEYBXsw.png', - u'НТРК "ИРТА"': logobase + 'd8zPTPLcK87xhBnGVFhgkuwFBY2TnK.png', u'Нано ТВ': logobase + 'QuURIfJUmXegxsHMYqMivVwxizbfKd.png', u'Наука 2.0': logobase + 'ypWbqYqKApM8cnDK1FibvQgpmgEay9.png', + u'Наше HD': logobase + 'NyV9o2C4xIMFxxYUQsS1qsdAtkqz0k.png', u'Наше любимое кино': logobase + 'LSR5M6VxB0YDwv6803zrGFkq7vGQ3J.png', + u'Наше ТВ': logobase + 'O7Wx8kIB2aXEEmHLwBFIUhxn8B5WQF.png', u'Ника ТВ': logobase + 'pb3d3rBN4qW7ggzsosbAZXflfIv0Ty.png', + u'НЛО ТВ': logobase + '2VGhYruaQo19G1NLGoOiTrwmPxef7d.png', u'Новороссия ТВ': logobase + 'zUchDq13UVJRmlwAl3feV8cgKHYSyE.png', u'Новый канал': logobase + 'k7YdHhVpFZPIkBMXS2P2O2TkZSPf0y.png', u'Ностальгия': logobase + 'tIfiXoDaXoZevuGu9pZJSvX8unv1xl.png', u'Ночной клуб': logobase + 'nXifSdkxHJVKI4SKtgtBQmCSHXtgOt.png', - u'ОТР': logobase + 'CqxKorK72v3ULbWkB3ZOhdte0duYZa.png', + u'НСТ': logobase + 'fKYzdlWRz68qd9mRZnWuxMY73EyaSz.png', + u'НТВ': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', + u'НТВ (+2)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', + u'НТВ (+3)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', + u'НТВ HD': logobase + 'zdJ3ye6d3UWl5a56zm6LjqYH6ziSOs.png', + u'НТВ Мир': logobase + 'ykpU49Gl1akVkgiVSsCm5B4TjXvQ64.png', + u'НТВ+ Кино плюс': logobase + 'cnz8ZMypP2HV6phwv3rkSVQ7CgJExi.png', + u'НТВ+ Киноклуб': logobase + 'nRnksgRuhojvbFDqh5KZ30XJQ4iyFO.png', + u'НТВ+ Кинохит': logobase + 'iQ0I0OnN11zzfQc5yivSJtXLiV0n8J.png', + u'НТВ+ Наше кино': logobase + 'UXJcZjVdZVIzciVHgGT3e6XxdRaBsD.png', + u'НТВ+ Наше новое кино': logobase + 'RcsEz5rIV3hQ6cwn6hqiwNAEOh1zNp.png', + u'НТВ+ Премьера': logobase + 'lDiI54Y3LjIAOg5VV0adicP3OJrdgo.png', + u'НТН (Украина)': logobase + 'LpQE1Odb1EoH5dJ90gWjItVyEYBXsw.png', + u'ОНТ Беларусь': logobase + 'a84If8XdqSFa6nHpegdujt52vAGNJW.png', u'Оплот 2': logobase + 'EqwpuUgrI6Wl6JVDK2fLtXkNaqOXeU.png', u'Оплот ТВ': logobase + 'gvofGxTug45qSt1vsX0BPzQxGTrwTr.png', u'Оружие': logobase + 'CyDUCmYXK8WS2kXCX5kiAOFejnlwoP.png', u'Остросюжетное HD': logobase + 'mxF7CZsqsDRMMK4pN8ekdccEgvEsZC.png', + u'ОТВ': logobase + 'tjw4uBPEbAHEkB75vTGHUsFtQ4nEKj.png', + u'ОТР': logobase + 'CqxKorK72v3ULbWkB3ZOhdte0duYZa.png', + u'Охота и рыбалка': logobase + '5l2P20J6ebTh0ptOr27Hh704niP3nU.png', u'Охотник и рыболов': logobase + 'Ws2ddPI0b5Ie7PymoPUsboVlz9lYMS.png', + u'Охотник и рыболов HD': logobase + 'O0wexOqiQXKMwr2coDMKWvJEb4zLQ1.png', u'Парк развлечений': logobase + 'beyfqyeacrFG0PrOeKUQhzQ4bV6Q5d.png', u'Первый автомобильный (Украина)': logobase + 'oZTXrmNOxeJIVSbnuxqbiuAL3voXYa.png', u'Первый городской (Киров)': logobase + 'sxUNuJVQpUjRMmASa5TvwlGykSBAkY.png', u'Первый городской Одесса': logobase + 'vBOI3YTA4FDLD0c7BHHjq476p9GMCZ.png', u'Первый деловой': logobase + 'a1Qf3MpxC9FPD68Tj8vtUTNK8P25xr.png', + u'Первый канал': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', + u'Первый канал (+2)': logobase + 'Lo2zy8h0msIieULsbHOjV1oSInHogr.png', u'Первый канал (+4)': logobase + 'nHJycH0CkOhPeZ9DmB47iSMWP5HyWz.png', u'Первый канал (+6)': logobase + 'xEhi4YWxLlIcHq33Y44NrYvyHRArwa.png', + u'Первый канал (Евразия)': logobase + 'oPB8TcSFAuJtk4hBWgAqC9yNjMpfsi.png', u'Первый канал (Европа)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (СНГ)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал HD': logobase + 'VxAFWzh1y88c8Aqa17TsxD2IO5pqoi.png', - u'Первый канал': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', + u'Первый Метео': logobase + 'vdA7FKd1SCYhX4ovvzjQEHFdW3uA5X.png', u'Первый музыкальный HD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', u'Первый музыкальный UHD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', - u'Первый музыкальный Россия HD': logobase + 'h7EDhdGypKmtfEP98O052SLlXUCcXt.png', - u'Первый музыкальный Россия': logobase + 'MkX2WG1zhZ2KcYFdL0xWH1T4xkO7UW.png', u'Первый музыкальный канал': logobase + 'gYpYhzD3akuKSFpRmkh2p36pXnqHoW.png', - u'Первый национальный (Украина)': logobase + '8yGRnEG4pNYMLFDVekA2yeOAX1lGZ2.png', + u'Первый музыкальный Россия HD': logobase + 'h7EDhdGypKmtfEP98O052SLlXUCcXt.png', u'Первый образовательный': logobase + '1kXxtStMuodaPU09H3rla3ry3QA2Wr.png', - u'Перец (+4)': logobase + '28.png', - u'Перец': logobase + '28.png', + u'ПИК': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', + u'ПИК HD': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', u'Пиксель ТВ': logobase + 'BdCXB7wPZMNvlWzB5xEFzmsYUXcfXW.png', + u'Планета': logobase + 'o7wnxC58C4KfCnX0hh0il0LTRlPfSg.png', u'ПлюсПлюс': logobase + '6gVIy7RMokFO61iVawgwbthe5mhgqm.png', - u'Право ТВ': logobase + 'jqV4vr8830fm6lYlX9F7w3tRvcrRra.png', u'Просвещение': logobase + 'Fpx3Vqqk2VNcXl4YjsfO53XscWadvF.png', u'Психология 21': logobase + 'AyLAdiqcKu5X8ykdLf2bO9HsxMlJdO.png', + u'Пятница': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', u'Пятница (+2)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', - u'Пятница (+4)': logobase + 'fF9FYWNiHFfuR1ZrkaboYHwi1O37TJ.png', u'Пятница (+7)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', - u'Пятница': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', - u'Пятый канал (+4)': logobase + 'tUE3C0hSxn7AxGHhST36CWi6HgJbIi.png', u'Пятый канал': logobase + 'nIUDYY41OO4Xo0ntGpGv2rfpOR5ngt.png', - u'РБК': logobase + 'JUMDXZxxB3UiVpMpU8t0aCpbVzxTmP.png', - u'РЕН ТВ (+2)': logobase + 'xwSFxBlid4YhPjZl8ibcIeTlzP0VVS.png', - u'РЕН ТВ (+4) (резерв)': logobase + '2Z3hcLqC0pQC9gLkuZSl5WkHfS5HYb.png', - u'РЕН ТВ (+4)': logobase + 'BE7n1y2cjisjflpQuMdC9P3c79rWb7.png', - u'РЕН ТВ': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', - u'РИА Новости': logobase + 'DixgG6tVZzcVHO2LPQEx3QrtfoVah3.png', u'Рада Украина': logobase + 'hBFJBYNiqZUom0ooVtNEJKliZwfioO.png', u'Радость моя': logobase + 'VRylZFYgFq7AL0FWcbf5JVOX3desn3.png', + u'РБК': logobase + 'JUMDXZxxB3UiVpMpU8t0aCpbVzxTmP.png', + u'РЕН ТВ': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', + u'РЕН ТВ (+3)': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', + u'РЕН ТВ (+4)': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', u'Ретро ТВ': logobase + 'axrNIB7372SHIRwqT0jBbfyvjSoZ7I.png', - u'Россия 1 (+4)': logobase + 'DL17FIS3R8m6eWTwFvdDYualmxvkGV.png', + u'РЖД ТВ': logobase + 'qa6177IUBWR0hYt1HKXxlMrF5MStOb.png', + u'РИА Новости': logobase + 'DixgG6tVZzcVHO2LPQEx3QrtfoVah3.png', u'Россия 1': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', + u'Россия 1 (+2)': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', + u'Россия 1 (+4)': logobase + 'ilXh9tpOhLtf4YREIyUACXh8TCxQVT.png', + u'Россия 1 (+6)': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', u'Россия 24': logobase + 'LWfGV6eICPYL7psaBfw2dOgGrOtHFS.png', u'Россия HD': logobase + 'ghvqmVpPWqn9x6POAm9UJBvXFzTrqN.png', - u'Россия К (+4)': logobase + 'lzLdqpUZ8iHL9JEV7vQGG1gSlyswfB.png', u'Россия К': logobase + 'W9pWrec1BOJTmj8okrFeyM44wcpyd4.png', + u'Россия К (+2)': logobase + 'rVrDeCUhT4RPKIa2h9qRtnDon3Pd9X.png', u'Россия РТР (Украина)': logobase + '5o9OWeEw90hM5ouECuTLwj5QP8MwU3.png', + u'Русская комедия': logobase + '4Q3vGcJh5o0cgJB18scb90ogvL6OYV.png', u'Русская ночь': logobase + '9Sh9bJuj6js5AJsypAd6UvwnsIB25R.png', u'Русский бестселлер': logobase + 'b5JXaosgmcanh9EVJg52yBefvdLQF7.png', u'Русский детектив': logobase + '7I7VjbsFMIkZdoSbHFXiKEVZKNUbOM.png', @@ -501,62 +533,73 @@ u'Русский роман': logobase + '2smriIFxtj7Ojh4jyZq0K1XrT98XjS.png', u'Русский экстрим': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Рыжий': logobase + 'wfBSy60qHaPSKPpTfrNv9Q167iHIPu.png', - u'СТБ': logobase + 'saZlIDrdaXWoiQa8sfZp2bEAeH0kXk.png', - u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', - u'СТС (+4)': logobase + 'Tl0KbPAKgErgJHEw9yo3VMoc35EWZt.png', - u'СТС Love': logobase + 'iciJHbEmJ1hHXAMhzC9cRWhmh9gH0L.png', - u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', u'Санкт-Петербург': logobase + 'sb81YtPOvlHidztMnC5tZPSKkb1uMI.png', u'Сарафан ТВ': logobase + 'LsYzwEOUspoxkY2hrTSy9zKqvpWlY8.png', + u'Семейное HD': logobase + 'ORgdUno4IvNQYiShiEY4srF16JguLs.png', + u'СК1 Житомир HD': logobase + 'oGKMU5pGcZElcU6sLzuDZ4ZEoSislJ.png', u'Совершенно секретно': logobase + 'BZJQEpa6Y4KL9tQjPHAIxbodw0KAyN.png', u'Сонце': logobase + 'TJXJVeoBFRMFrUgzpPW4dunJL6XSzn.png', u'Союз': logobase + 'YpsuBorUwulPHW3nI8O6nKETnEVB83.png', u'Спас ТВ': logobase + 'pAFeyS1iCV4BybnpnnwjoKm0y0zvaA.png', u'Спорт 1 (Украина)': logobase + 'XqwvMS8Hn0mOpbh79esrIqELTsvo5b.png', - u'Спорт 1 HD': logobase + 'cqsjEb2YlMBsTNJPvKmkxmAWLkfNmp.png', - u'Спорт 1': logobase + 'UNqCK5IiCxetsHsN7eMApPOzhaM9v0.png', u'Спорт 2 (Украина)': logobase + 'q0PokCXx6jtCHEPMvE42I0pD3ZNY0o.png', - u'Спорт': logobase + 'InpgSRB4SoFzJSAOkLpGCMayuYTtVm.png', + u'СТБ': logobase + 'saZlIDrdaXWoiQa8sfZp2bEAeH0kXk.png', u'Страна': logobase + '5G27bahViND43dD1VlkaKlQRsYOqwL.png', + u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', + u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', + u'СТС (+2)': logobase + 'FWNPupxL8N0STWKYEryyiV5sDFN2tS.png', + u'СТС (+3)': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', + u'СТС Love': logobase + 'iciJHbEmJ1hHXAMhzC9cRWhmh9gH0L.png', u'ТБН': logobase + 'r9O7HmwQbFR4oKMH9yKAogE8xBzwz4.png', - u'ТВ 3 (+2)': logobase + '427.png', u'ТВ 3': logobase + '427.png', + u'ТВ 3 (+3)': logobase + '427.png', + u'ТВ 3 (+3)': logobase + 'YzjZZoc01zFHW2bm6CuP2jkHo0byV3.png', u'ТВ2-ТВ': logobase + 'fFl6h6QUwFKoI3N4lqEyhNLdACGkNq.png', - u'ТВЦ (+4)': logobase + 'dR7hMBOIq0MDGMkydFuksHGLNIWz7U.png', + u'Тверской проспект': logobase + 'k6RPWpDIwfsOZ5qkqHF5ZdOf5GqfmH.png', u'ТВЦ': logobase + 'QEpQTskZ9hcfI0rgD8osHVYSv58pde.png', u'ТДК': logobase + 'eSrHE6Gws4U6JxhFXA3mQ4iDVc0SwS.png', + u'Теледом HD': logobase + 'XviuCfRo0T4WFTOhFaC978AwZ1a3Ge.png', + u'Телекафе': logobase + 'fYRFV5oY197jXcyModfWVs0AlrCOIs.png', + u'Телепутешествия': logobase + 'fz4bqwLySJAQkUN7l2EPKNqyvilfRD.png', u'ТЕТ': logobase + 'jp0YxRwXOyMWgVfDAyQaXNwle90sV3.png', + u'Техно 24': logobase + 'JbUGHLuuZa3WQbjtbzUo0cDZkGnLRK.png', u'ТНВ-Планета': logobase + 'vBMv8AtIpDhBGQLjPoxkko3baWMFac.png', u'ТНВ-Татарстан': logobase + 'kMdYm3qFLgK52EV0ymvRBB43peSrj9.png', - u'ТНТ (+4)': logobase + 'eU71rcMDW3T6Ra5K7Ahh16wGf1gPvr.png', - u'ТНТ (+7)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', - u'ТНТ Comedy': logobase + 'IihdBuOBtjIeeUli9g5jR0196S9Ryk.png', u'ТНТ': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', + u'ТНТ (+2)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', + u'ТНТ (+4)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', + u'ТНТ (+7)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', + u'ТНТ International (Европа)': logobase + 'd4loXdWqOPiwF7thzyBXX8JSspfVjU.png', + u'ТНТ4': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', + u'ТНТ4 (+2)': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', + u'Тонус ТВ': logobase + 'bE8WfReOerYTIbqPOo6VD2ajrFdOBT.png', + u'Точка ТВ': logobase + 'bDZ1EY6wCnzKYyh7LjonM4fdy9ExvD.png', u'ТРК Киев': logobase + 'qW0p5z3De7COmSxTmvJ4ZA2wOuSJjg.png', u'ТРК Украина': logobase + '0co3dwhFDhoCVeTbfMV8ASYFYxSrWM.png', u'ТРО Союза': logobase + 'xAXy9iMyJ4wa2wmugJvbZuDIzc9pVz.png', - u'Теледом HD': logobase + 'XviuCfRo0T4WFTOhFaC978AwZ1a3Ge.png', - u'Телекафе': logobase + 'fYRFV5oY197jXcyModfWVs0AlrCOIs.png', - u'Телепутешествия': logobase + 'fz4bqwLySJAQkUN7l2EPKNqyvilfRD.png', - u'Техно 24': logobase + 'JbUGHLuuZa3WQbjtbzUo0cDZkGnLRK.png', - u'Тонус ТВ': logobase + 'bE8WfReOerYTIbqPOo6VD2ajrFdOBT.png', - u'Трофей': logobase + 'pOZb3d5BA6OkYL7qpTS4AUiihdLgZ5.png', + u'Трофей': logobase + 'tQTWwjNBC8aLLWiWZfCj43BhWNH51I.png', + u'Трофей HD': logobase + 'cPi9B0icZpuvSRQiH0Kk6rNY8r1X65.png', + u'ТТС': logobase + 'crsSIipA6N288sjn4EvUyOyTd0ed9A.png', u'Улыбка ребенка': logobase + 'P8aPFN50uJWJHkrqFGb7wgzfaTHUOO.png', u'Унiан': logobase + 'fhpFrTDoI9xx7UlK65KAjAbdTGehLL.png', u'Усадьба': logobase + '5yIxLQzQyZnH5EJcwpSGb28QuRTSFH.png', u'Успех': logobase + 'RLcfsouYRxTNrQT97AOPIYfSneJyB6.png', u'Феникс+ Кино': logobase + 'idiNkkBsxLwxWCF2VZrc9LQEevKh0d.png', + u'Футбол': logobase + '472.png', u'Футбол 1 (Украина)': logobase + 'AMKtYwcgSAX5mTcPdhQDe4he18Jz7S.png', u'Футбол 1 HD (Украина)': logobase + 'hZaWPKLVxTqUWZk0LTmLi1K1WUzX85.png', u'Футбол 2 (Украина)': logobase + 'PUXTI9mKcs49JnEENkh95KoKqt9VNg.png', u'Футбол 2 HD (Украина)': logobase + 'TTvGrBoRM07MHY4q6bSwfzVuDKEGTi.png', - u'Футбол': logobase + '472.png', u'ЧГТРК Грозный': logobase + 'LqDNdQj6nf4MraZztT6ZnACn7yOJpV.png', + u'Че': logobase + 'Hv36ZG48lg1mm2wdxAo3ju1EFS41Ga.png', + u'Че (+2)': logobase + 'N7R86wgoPSELhZ9jCeqaykayjU8mbB.png', + u'Че (+4)': logobase + 'Hv36ZG48lg1mm2wdxAo3ju1EFS41Ga.png', + u'ЧП-Инфо': logobase + 'Xy7mLl3exaBKuLlDGdrRls6hyR7mSw.png', u'Шансон ТВ': logobase + 'VY0TyCCkKOj5b8BhBJjT020sQoxL9F.png', u'Эгоист ТВ': logobase + 'moG8uExVh4nw3MN7dmGFdysJHBWLk6.png', + u'Ю': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', u'Ю (+2)': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', u'Ю (+7)': logobase + 'lS7OcLo9fsdDFdDFDvdlM3OE3Uu8Tj.png', - u'Ю': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', u'Юмор ТВ': logobase + '6VFA1SVxeFHUsGaKPbNxWZREDkGeZw.png', u'Ямал Регион': logobase + 'xapccCaMjlT6JEAkZmk27wzCXlEU2m.png' } \ No newline at end of file diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index e34b376..4b00d60 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -30,7 +30,7 @@ class P2pproxy(AceProxyPlugin): - handlers = ('channels', 'archive', 'xbmc.pvr') + handlers = ('channels', 'archive', 'xbmc.pvr', 'logos') logger = logging.getLogger('plugin_p2pproxy') @@ -307,6 +307,26 @@ def handle(self, connection, headers_only=False): connection.send_header('Content-Length', str(len(records_list))) connection.end_headers() connection.wfile.write(records_list) + elif connection.reqtype == 'logos': # Used to generate logomap for the torrenttv plugin + session = TorrentTvApi.auth(config.email, config.password) + translations_list = TorrentTvApi.translations(session, 'all') + last = translations_list[-1] + connection.send_response(200) + connection.send_header('Content-Type', 'text/plain;charset=utf-8') + connection.end_headers() + connection.wfile.write("logobase = 'http://torrent-tv.ru/uploads/'\n") + connection.wfile.write("logomap = {\n") + + for channel in translations_list: + name = channel.getAttribute('name').encode('utf-8') + logo = channel.getAttribute('logo').encode('utf-8') + connection.wfile.write(" u'%s': logobase + '%s'" % (name, logo)) + if not channel == last: + connection.wfile.write(",\n") + else: + connection.wfile.write("\n") + + connection.wfile.write("}\n") def get_param(self, key): if key in self.params: From 1fa4500dd1a689fafc558c35a5c29cea57ee4314 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 13 Mar 2016 01:28:54 +0300 Subject: [PATCH 10/95] Do not authenticate for each request - reuse the previous session. --- plugins/torrenttv_api.py | 95 +++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py index c3a8515..44963d1 100644 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -3,13 +3,15 @@ Torrent-TV API communication class Forms requests to API, checks result for errors and returns in desired form (lists or raw data) """ +from unittest.signals import _results __author__ = 'miltador' import urllib2 import socket import random import xml.dom.minidom as dom - +import logging +import time class TorrentTvApiException(Exception): """ @@ -33,6 +35,12 @@ class TorrentTvApi(object): 11: 'Региональные', 12: 'Религиозные' } + + session = None + sessionMaxIdle = 600 + sessionLastActive = 0.0 + sessionEmail = None + sessionPassword = None @staticmethod def auth(email, password, raw=False): @@ -45,13 +53,26 @@ def auth(email, password, raw=False): :param raw: if True returns unprocessed data :return: unique session string """ + session = TorrentTvApi.session + sessionLastActive = TorrentTvApi.sessionLastActive + if session and (time.time() - sessionLastActive) < TorrentTvApi.sessionMaxIdle: + TorrentTvApi.sessionLastActive = time.time() + logging.debug("Reusing previous session: " + session) + return session + logging.debug("Creating new session") + TorrentTvApi.session = None xmlresult = TorrentTvApi._result( 'v3/auth.php?username=' + email + '&password=' + password + '&application=tsproxy&guid=' + str(random.randint(100000000,199999999))) if raw: return xmlresult res = TorrentTvApi._check(xmlresult) session = res.getElementsByTagName('session')[0].firstChild.data + TorrentTvApi.session = session + TorrentTvApi.sessionEmail = email + TorrentTvApi.sessionPassword - password + TorrentTvApi.sessionLastActive = time.time() + logging.debug("New session created: " + session) return session @staticmethod @@ -65,11 +86,22 @@ def translations(session, translation_type, raw=False): :param raw: if True returns unprocessed data :return: translations list """ - xmlresult = TorrentTvApi._result( - 'v3/translation_list.php?session=' + session + '&type=' + translation_type) + if raw: - return xmlresult - res = TorrentTvApi._check(xmlresult) + try: + xmlresult = TorrentTvApi._result( + 'v3/translation_list.php?session=' + session + '&type=' + translation_type) + TorrentTvApi._check(xmlresult) + return xmlresult + except TorrentTvApiException: + TorrentTvApi._resetSession() + xmlresult = TorrentTvApi._result( + 'v3/translation_list.php?session=' + session + '&type=' + translation_type) + TorrentTvApi._check(xmlresult) + return xmlresult + + res = TorrentTvApi._checkedresult( + 'v3/translation_list.php?session=' + session + '&type=' + translation_type) translationslist = res.getElementsByTagName('channel') return translationslist @@ -84,11 +116,22 @@ def records(session, channel_id, date, raw=False): :param raw: if True returns unprocessed data :return: records list """ - xmlresult = TorrentTvApi._result( - 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) + if raw: - return xmlresult - res = TorrentTvApi._check(xmlresult) + try: + xmlresult = TorrentTvApi._result( + 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) + TorrentTvApi._check(xmlresult) + return xmlresult + except TorrentTvApiException: + TorrentTvApi._resetSession() + xmlresult = TorrentTvApi._result( + 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) + TorrentTvApi._check(xmlresult) + return xmlresult + + res = TorrentTvApi._checkedresult( + 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) recordslist = res.getElementsByTagName('channel') return recordslist @@ -101,11 +144,18 @@ def archive_channels(session, raw=False): :param raw: if True returns unprocessed data :return: archive channels list """ - xmlresult = TorrentTvApi._result( - 'v3/arc_list.php?session=' + session) if raw: - return xmlresult - res = TorrentTvApi._check(xmlresult) + try: + xmlresult = TorrentTvApi._result('v3/arc_list.php?session=' + session) + TorrentTvApi._check(xmlresult) + return xmlresult + except TorrentTvApiException: + TorrentTvApi._resetSession() + xmlresult = TorrentTvApi._result('v3/arc_list.php?session=' + session) + TorrentTvApi._check(xmlresult) + return xmlresult + + res = TorrentTvApi._checkedresult('v3/arc_list.php?session=' + session) archive_channelslist = res.getElementsByTagName('channel') return archive_channelslist @@ -118,9 +168,8 @@ def stream_source(session, channel_id): :param channel_id: id of channel in translations list (see translations() method) :return: type of stream and source """ - xmlresult = TorrentTvApi._result( + res = TorrentTvApi._checkedresult( 'v3/translation_stream.php?session=' + session + '&channel_id=' + channel_id) - res = TorrentTvApi._check(xmlresult) stream_type = res.getElementsByTagName('type')[0].firstChild.data source = res.getElementsByTagName('source')[0].firstChild.data return stream_type.encode('utf-8'), source.encode('utf-8') @@ -134,9 +183,8 @@ def archive_stream_source(session, record_id): :param record_id: id of record in records list (see records() method) :return: type of stream and source """ - xmlresult = TorrentTvApi._result( + res = TorrentTvApi._checkedresult( 'v3/arc_stream.php?session=' + session + '&record_id=' + record_id) - res = TorrentTvApi._check(xmlresult) stream_type = res.getElementsByTagName('type')[0].firstChild.data source = res.getElementsByTagName('source')[0].firstChild.data return stream_type.encode('utf-8'), source.encode('utf-8') @@ -158,6 +206,14 @@ def _check(xmlresult): raise TorrentTvApiException('API returned error: ' + error) return res + @staticmethod + def _checkedresult(request): + try: + return TorrentTvApi._check(TorrentTvApi._result(request)) + except TorrentTvApiException: + TorrentTvApi._resetSession() + return TorrentTvApi._check(TorrentTvApi._result(request)) + @staticmethod def _result(request): """ @@ -172,3 +228,8 @@ def _result(request): return result except (urllib2.URLError, socket.timeout) as e: raise TorrentTvApiException('Error happened while trying to access API: ' + repr(e)) + + @staticmethod + def _resetSession(): + TorrentTvApi.session = None + TorrentTvApi.auth(TorrentTvApi.sessionEmail, TorrentTvApi.sessionPassword) From 7b1ab4b6a9e51051ccda479fc6744422792bd077 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 13 Mar 2016 10:19:18 +0300 Subject: [PATCH 11/95] Fixed typo. --- plugins/torrenttv_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py index 44963d1..6711dc5 100644 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -70,7 +70,7 @@ def auth(email, password, raw=False): session = res.getElementsByTagName('session')[0].firstChild.data TorrentTvApi.session = session TorrentTvApi.sessionEmail = email - TorrentTvApi.sessionPassword - password + TorrentTvApi.sessionPassword = password TorrentTvApi.sessionLastActive = time.time() logging.debug("New session created: " + session) return session From 0f2ed041794bd127bcba0787ed7989c16937c692 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 13 Mar 2016 10:46:01 +0300 Subject: [PATCH 12/95] Use START PID if we know the content id --- acehttp.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/acehttp.py b/acehttp.py index 8835366..6c4a285 100755 --- a/acehttp.py +++ b/acehttp.py @@ -258,7 +258,8 @@ def handleRequest(self, headers_only): self.url = None self.path_unquoted = urllib2.unquote(self.splittedpath[2]) - cid = self.getCid(self.reqtype, self.path_unquoted) + contentid = self.getCid(self.reqtype, self.path_unquoted) + cid = contentid if contentid else self.path_unquoted logger.debug("CID: " + cid) self.client = Client(cid, self) self.vlcid = cid @@ -281,7 +282,9 @@ def handleRequest(self, headers_only): # Initializing AceClient if shouldStart: - if self.reqtype == 'pid': + if contentid: + self.client.ace.START('PID', {'content_id': contentid}) + elif self.reqtype == 'pid': self.client.ace.START( self.reqtype, {'content_id': self.path_unquoted, 'file_indexes': self.params[0]}) elif self.reqtype == 'torrent': @@ -405,7 +408,7 @@ def getCid(self, reqtype, url): except: logging.debug("Failed to get CID from WEB API") - return url if cid == '' else cid + return None if not cid or cid == '' else cid class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): From 3a8cead4dd5f74122bf29cb37f4757ac88f1b7e6 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 13 Mar 2016 20:26:41 +0300 Subject: [PATCH 13/95] Redesigned ttv api --- plugins/config/p2pproxy.py | 3 + plugins/p2pproxy_plugin.py | 30 ++--- plugins/torrenttv_api.py | 222 ++++++++++++++++++++----------------- 3 files changed, 134 insertions(+), 121 deletions(-) diff --git a/plugins/config/p2pproxy.py b/plugins/config/p2pproxy.py index b12e97b..a45c00a 100644 --- a/plugins/config/p2pproxy.py +++ b/plugins/config/p2pproxy.py @@ -22,6 +22,9 @@ # Insert your torrent-tv account password password = 'ReplaceMe' +# Session timeout +sessiontimeout = 1800 + # Generate logo with full path (e.g. http://torrent-tv.ru/uploads/ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png) # or put only the logo file name (e.g. ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png) # This option is only for m3u playlists. diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 4b00d60..e5cc302 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -37,6 +37,7 @@ class P2pproxy(AceProxyPlugin): def __init__(self, AceConfig, AceStuff): super(P2pproxy, self).__init__(AceConfig, AceStuff) self.params = None + self.api = TorrentTvApi(config.email, config.password, config.sessiontimeout) def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Handling request') @@ -74,8 +75,7 @@ def handle(self, connection, headers_only=False): stream_url = None - session = TorrentTvApi.auth(config.email, config.password) - stream_type, stream = TorrentTvApi.stream_source(session, channel_id) + stream_type, stream = self.api.stream_source(channel_id) if stream_type == 'torrent': stream_url = re.sub('^(http.+)$', @@ -101,8 +101,7 @@ def handle(self, connection, headers_only=False): if not param_filter: param_filter = 'all' # default filter - session = TorrentTvApi.auth(config.email, config.password) - translations_list = TorrentTvApi.translations(session, param_filter) + translations_list = self.api.translations(param_filter) playlistgen = PlaylistGenerator() P2pproxy.logger.debug('Generating requested m3u playlist') @@ -144,8 +143,7 @@ def handle(self, connection, headers_only=False): if not param_filter: param_filter = 'all' # default filter - session = TorrentTvApi.auth(config.email, config.password) - translations_list = TorrentTvApi.translations(session, param_filter, True) + translations_list = self.api.translations(param_filter, True) P2pproxy.logger.debug('Exporting') connection.send_response(200) @@ -167,8 +165,7 @@ def handle(self, connection, headers_only=False): if not headers_only: return - session = TorrentTvApi.auth(config.email, config.password) - translations_list = TorrentTvApi.translations(session, 'all', True) + translations_list = self.api.translations('all', True) P2pproxy.logger.debug('Exporting') connection.wfile.write(translations_list) elif connection.reqtype == 'archive': # /archive/ branch @@ -181,8 +178,7 @@ def handle(self, connection, headers_only=False): if headers_only: connection.end_headers() else: - session = TorrentTvApi.auth(config.email, config.password) - archive_channels = TorrentTvApi.archive_channels(session, True) + archive_channels = self.api.archive_channels(True) P2pproxy.logger.debug('Exporting') connection.send_header('Content-Length', str(len(archive_channels))) connection.end_headers() @@ -202,8 +198,7 @@ def handle(self, connection, headers_only=False): stream_url = None - session = TorrentTvApi.auth(config.email, config.password) - stream_type, stream = TorrentTvApi.archive_stream_source(session, record_id) + stream_type, stream = self.api.archive_stream_source(record_id) if stream_type == 'torrent': stream_url = re.sub('^(http.+)$', @@ -242,9 +237,8 @@ def handle(self, connection, headers_only=False): connection.end_headers() return - session = TorrentTvApi.auth(config.email, config.password) - records_list = TorrentTvApi.records(session, param_channel, d.strftime('%d-%m-%Y')) - channels_list = TorrentTvApi.archive_channels(session) + records_list = self.api.records(param_channel, d.strftime('%d-%m-%Y')) + channels_list = self.api.archive_channels() playlistgen = PlaylistGenerator() P2pproxy.logger.debug('Generating archive m3u playlist') @@ -301,15 +295,13 @@ def handle(self, connection, headers_only=False): if headers_only: connection.end_headers() else: - session = TorrentTvApi.auth(config.email, config.password) - records_list = TorrentTvApi.records(session, param_channel, d.strftime('%d-%m-%Y'), True) + records_list = self.api.records(param_channel, d.strftime('%d-%m-%Y'), True) P2pproxy.logger.debug('Exporting') connection.send_header('Content-Length', str(len(records_list))) connection.end_headers() connection.wfile.write(records_list) elif connection.reqtype == 'logos': # Used to generate logomap for the torrenttv plugin - session = TorrentTvApi.auth(config.email, config.password) - translations_list = TorrentTvApi.translations(session, 'all') + translations_list = self.api.translations('all') last = translations_list[-1] connection.send_response(200) connection.send_header('Content-Type', 'text/plain;charset=utf-8') diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py index 6711dc5..2fb76d9 100644 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -3,15 +3,16 @@ Torrent-TV API communication class Forms requests to API, checks result for errors and returns in desired form (lists or raw data) """ -from unittest.signals import _results __author__ = 'miltador' import urllib2 import socket import random import xml.dom.minidom as dom +import json import logging import time +import threading class TorrentTvApiException(Exception): """ @@ -36,14 +37,18 @@ class TorrentTvApi(object): 12: 'Религиозные' } - session = None - sessionMaxIdle = 600 - sessionLastActive = 0.0 - sessionEmail = None - sessionPassword = None + API_URL = 'http://1ttvapi.top/v3/' + + def __init__(self, email, password, maxIdle): + self.email = email + self.password = password + self.maxIdle = maxIdle + self.session = None + self.lastActive = 0.0 + self.lock = threading.RLock() + self.log = logging.getLogger("TTV API") - @staticmethod - def auth(email, password, raw=False): + def auth(self): """ User authentication Returns user session that can be used for API requests @@ -53,30 +58,22 @@ def auth(email, password, raw=False): :param raw: if True returns unprocessed data :return: unique session string """ - session = TorrentTvApi.session - sessionLastActive = TorrentTvApi.sessionLastActive - if session and (time.time() - sessionLastActive) < TorrentTvApi.sessionMaxIdle: - TorrentTvApi.sessionLastActive = time.time() - logging.debug("Reusing previous session: " + session) - return session - - logging.debug("Creating new session") - TorrentTvApi.session = None - xmlresult = TorrentTvApi._result( - 'v3/auth.php?username=' + email + '&password=' + password + '&application=tsproxy&guid=' + str(random.randint(100000000,199999999))) - if raw: - return xmlresult - res = TorrentTvApi._check(xmlresult) - session = res.getElementsByTagName('session')[0].firstChild.data - TorrentTvApi.session = session - TorrentTvApi.sessionEmail = email - TorrentTvApi.sessionPassword = password - TorrentTvApi.sessionLastActive = time.time() - logging.debug("New session created: " + session) - return session - - @staticmethod - def translations(session, translation_type, raw=False): + with self.lock: + if self.session and (time.time() - self.lastActive) < self.maxIdle: + self.lastActive = time.time() + self.log.debug("Reusing previous session: " + self.session) + return self.session + + self.log.debug("Creating new session") + self.session = None + req = TorrentTvApi.API_URL + 'auth.php?typeresult=json&username=' + self.email + '&password=' + self.password + '&application=tsproxy&guid=' + str(random.randint(100000000,199999999)) + result = self._jsoncheck(json.loads(urllib2.urlopen(req, timeout=10).read())) + self.session = result['session'] + self.lastActive = time.time() + self.log.debug("New session created: " + self.session) + return self.session + + def translations(self, translation_type, raw=False): """ Gets list of translations Translations are basically TV channels @@ -89,24 +86,18 @@ def translations(session, translation_type, raw=False): if raw: try: - xmlresult = TorrentTvApi._result( - 'v3/translation_list.php?session=' + session + '&type=' + translation_type) - TorrentTvApi._check(xmlresult) - return xmlresult + res = self._xmlresult('translation_list.php', '&type=' + translation_type) + self._checkxml(res) + return res except TorrentTvApiException: - TorrentTvApi._resetSession() - xmlresult = TorrentTvApi._result( - 'v3/translation_list.php?session=' + session + '&type=' + translation_type) - TorrentTvApi._check(xmlresult) - return xmlresult - - res = TorrentTvApi._checkedresult( - 'v3/translation_list.php?session=' + session + '&type=' + translation_type) - translationslist = res.getElementsByTagName('channel') - return translationslist + res = self._xmlresult('translation_list.php', '&type=' + translation_type) + self._checkxml(res) + return res + else: + res = self._checkedxmlresult('translation_list.php', '&type=' + translation_type) + return res.getElementsByTagName('channel') - @staticmethod - def records(session, channel_id, date, raw=False): + def records(self, channel_id, date, raw=False): """ Gets list of available record for given channel and date @@ -116,27 +107,20 @@ def records(session, channel_id, date, raw=False): :param raw: if True returns unprocessed data :return: records list """ - if raw: try: - xmlresult = TorrentTvApi._result( - 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) - TorrentTvApi._check(xmlresult) - return xmlresult + res = self._xmlresult('arc_records.php', '&channel_id=' + channel_id + '&date=' + date) + self._checkxml(res) + return res except TorrentTvApiException: - TorrentTvApi._resetSession() - xmlresult = TorrentTvApi._result( - 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) - TorrentTvApi._check(xmlresult) - return xmlresult - - res = TorrentTvApi._checkedresult( - 'v3/arc_records.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) - recordslist = res.getElementsByTagName('channel') - return recordslist + res = self._xmlresult('arc_records.php', '&channel_id=' + channel_id + '&date=' + date) + self._checkxml(res) + return res + else: + res = self._xmlresult('arc_records.php', '&channel_id=' + channel_id + '&date=' + date) + return res.getElementsByTagName('channel') - @staticmethod - def archive_channels(session, raw=False): + def archive_channels(self, raw=False): """ Gets the channels list for archive @@ -144,23 +128,21 @@ def archive_channels(session, raw=False): :param raw: if True returns unprocessed data :return: archive channels list """ + if raw: try: - xmlresult = TorrentTvApi._result('v3/arc_list.php?session=' + session) - TorrentTvApi._check(xmlresult) - return xmlresult + res = self._xmlresult('arc_list.php', '') + self._checkxml(res) + return res except TorrentTvApiException: - TorrentTvApi._resetSession() - xmlresult = TorrentTvApi._result('v3/arc_list.php?session=' + session) - TorrentTvApi._check(xmlresult) - return xmlresult + res = self._xmlresult('arc_list.php', '') + self._checkxml(res) + return res + else: + res = self._xmlresult('arc_list.php', '') + return res.getElementsByTagName('channel') - res = TorrentTvApi._checkedresult('v3/arc_list.php?session=' + session) - archive_channelslist = res.getElementsByTagName('channel') - return archive_channelslist - - @staticmethod - def stream_source(session, channel_id): + def stream_source(self, channel_id): """ Gets the source for Ace Stream by channel id @@ -168,14 +150,13 @@ def stream_source(session, channel_id): :param channel_id: id of channel in translations list (see translations() method) :return: type of stream and source """ - res = TorrentTvApi._checkedresult( - 'v3/translation_stream.php?session=' + session + '&channel_id=' + channel_id) - stream_type = res.getElementsByTagName('type')[0].firstChild.data - source = res.getElementsByTagName('source')[0].firstChild.data + + res = self._checkedjsonresult('translation_stream.php', '&channel_id=' + channel_id) + stream_type = res['type'] + source = res['source'] return stream_type.encode('utf-8'), source.encode('utf-8') - @staticmethod - def archive_stream_source(session, record_id): + def archive_stream_source(self, record_id): """ Gets stream source for archive record @@ -183,14 +164,28 @@ def archive_stream_source(session, record_id): :param record_id: id of record in records list (see records() method) :return: type of stream and source """ - res = TorrentTvApi._checkedresult( - 'v3/arc_stream.php?session=' + session + '&record_id=' + record_id) - stream_type = res.getElementsByTagName('type')[0].firstChild.data - source = res.getElementsByTagName('source')[0].firstChild.data + + res = self._checkedjsonresult('arc_stream.php', '&record_id=' + record_id) + stream_type = res['type'] + source = res['source'] return stream_type.encode('utf-8'), source.encode('utf-8') - @staticmethod - def _check(xmlresult): + def _jsoncheck(self, jsonresult): + """ + Validates received API answer + Raises an exception if error detected + + :param jsonresult: API answer to check + :return: minidom-parsed xmlresult + :raise: TorrentTvApiException + """ + success = jsonresult['success'] + if success == '0' or not success: + error = jsonresult['error'] + raise TorrentTvApiException('API returned error: ' + error) + return jsonresult + + def _checkxml(self, xmlresult): """ Validates received API answer Raises an exception if error detected @@ -206,16 +201,37 @@ def _check(xmlresult): raise TorrentTvApiException('API returned error: ' + error) return res - @staticmethod - def _checkedresult(request): + def _checkedjsonresult(self, request, params): try: - return TorrentTvApi._check(TorrentTvApi._result(request)) + return self._jsoncheck(self._jsonresult(request, params)) except TorrentTvApiException: - TorrentTvApi._resetSession() - return TorrentTvApi._check(TorrentTvApi._result(request)) + self._resetSession() + return self._jsoncheck(self._jsonresult(request, params)) + + def _checkedxmlresult(self, request, params): + try: + return self._checkxml(self._xmlresult(request, params)) + except TorrentTvApiException: + self._resetSession() + return self._checkxml(self._xmlresult(request, params)) + + def _jsonresult(self, request, params): + """ + Sends request to API and returns the result in form of string + + :param request: API command string + :return: result of request to API + :raise: TorrentTvApiException + """ + try: + req = TorrentTvApi.API_URL + request + '?session=' + self.auth() + '&typeresult=json' + params + self.log.debug(req) + result = urllib2.urlopen(req, timeout=10).read() + return json.loads(result) + except (urllib2.URLError, socket.timeout) as e: + raise TorrentTvApiException('Error happened while trying to access API: ' + repr(e)) - @staticmethod - def _result(request): + def _xmlresult(self, request, params): """ Sends request to API and returns the result in form of string @@ -224,12 +240,14 @@ def _result(request): :raise: TorrentTvApiException """ try: - result = urllib2.urlopen('http://1ttvapi.top/' + request + '&typeresult=xml', timeout=10).read() + req = TorrentTvApi.API_URL + request + '?session=' + self.auth() + '&typeresult=xml' + params + self.log.debug(req) + result = urllib2.urlopen(req, timeout=10).read() return result except (urllib2.URLError, socket.timeout) as e: raise TorrentTvApiException('Error happened while trying to access API: ' + repr(e)) - @staticmethod - def _resetSession(): - TorrentTvApi.session = None - TorrentTvApi.auth(TorrentTvApi.sessionEmail, TorrentTvApi.sessionPassword) + def _resetSession(self): + with self.lock: + self.session = None + self.auth() From 9f9d77c74fa6c5e209097ea6455a5599cbc295ef Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 14 Mar 2016 19:21:36 +0300 Subject: [PATCH 14/95] Fixed torrents playback. Minor fixes and enhancements. --- aceclient/aceclient.py | 17 +++++++++++++---- aceclient/acemessages.py | 3 +-- aceclient/clientcounter.py | 6 ++---- aceconfig.py | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/aceclient/aceclient.py b/aceclient/aceclient.py index 8abc605..b75e8b4 100644 --- a/aceclient/aceclient.py +++ b/aceclient/aceclient.py @@ -114,6 +114,11 @@ def destroy(self): finally: self._shuttingDown.set() + def reset(self): + self._started_again = False + self._idleSince = time.time() + self._streamReaderState = None + def _write(self, message): try: logger = logging.getLogger("AceClient_write") @@ -178,11 +183,13 @@ def STOP(self): self._write(AceMessage.request.STOP) self._getResult() - def GETCID(self, datatype, url): + def LOADASYNC(self, datatype, url): self._result = AsyncResult() self._write(AceMessage.request.LOADASYNC(datatype.upper(), 0, {'url': url})) - contentinfo = self._getResult() - + return self._getResult() + + def GETCID(self, datatype, url): + contentinfo = self.LOADASYNC(datatype, url) self._cidresult = AsyncResult() self._write(AceMessage.request.GETCID(contentinfo.get('checksum'), contentinfo.get('infohash'), 0, 0, 0)) cid = self._cidresult.get(True, 5) @@ -341,7 +348,7 @@ def _recvData(self): elif self._recvbuffer.startswith(AceMessage.response.START): # START - if not self._seekback or (self._seekback and self._started_again): + if not self._seekback or self._started_again or not self._recvbuffer.endswith(' stream=1'): # If seekback is disabled, we use link in first START command. # If seekback is enabled, we wait for first START command and # ignore it, then do seeback in first EVENT position command @@ -353,6 +360,8 @@ def _recvData(self): self._resumeevent.set() except IndexError as e: self._url = None + else: + logger.debug("START received. Waiting for %s." % AceMessage.response.LIVEPOS) elif self._recvbuffer.startswith(AceMessage.response.STOP): pass diff --git a/aceclient/acemessages.py b/aceclient/acemessages.py index 3994605..86449d8 100644 --- a/aceclient/acemessages.py +++ b/aceclient/acemessages.py @@ -92,8 +92,7 @@ def START(command, params_dict): str(params_dict.get('file_indexes', '0')) + ' ' + \ str(params_dict.get('developer_id', '0')) + ' ' + \ str(params_dict.get('affiliate_id', '0')) + ' ' + \ - str(params_dict.get('zone_id', '0')) + ' ' + \ - str(params_dict.get('stream_id', '0')) + str(params_dict.get('zone_id', '0')) elif command == 'PID': return 'START PID ' + str(params_dict.get('content_id')) + ' ' + \ diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index ab487c8..c0bfa85 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -65,8 +65,7 @@ def delete(self, cid, client): try: client.ace.STOP() self.idleace = client.ace - self.idleace._idleSince = time.time() - self.idleace._streamReaderState = None + self.idleace.reset() except: client.ace.destroy() finally: @@ -91,8 +90,7 @@ def deleteAll(self, cid): try: clients[0].ace.STOP() self.idleace = clients[0].ace - self.idleace._idleSince = time.time() - self.idleace._streamReaderState = None + self.idleace.reset() except: clients[0].ace.destroy() finally: diff --git a/aceconfig.py b/aceconfig.py index 13eacd8..f4a63bd 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -144,7 +144,7 @@ class AceConfig(acedefconfig.AceDefConfig): # video stream delay. # Set it to 30 or so. # Works only with the newest versions of AceEngine! - videoseekback = 30 + videoseekback = 0 # Delay before closing Ace Stream connection when client disconnects # In seconds. videodestroydelay = 0 From dd66362ff184120d33b75ce7af86e56c675b74c3 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 14 Mar 2016 22:03:50 +0300 Subject: [PATCH 15/95] Redesigned the torrenttv plugin --- plugins/modules/PlaylistGenerator.py | 10 +-- plugins/torrenttv_plugin.py | 103 +++++++++++++++------------ 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py index 7742383..9a393ba 100644 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -46,18 +46,20 @@ def exportm3u(self, hostport, add_ts=False, empty_header=False, archive=False, h Exports m3u playlist ''' + if add_ts: + # Adding ts:// after http:// for some players + hostport = 'ts://' + hostport + if header is None: if not empty_header: itemlist = config.m3uheader else: itemlist = config.m3uemptyheader - if add_ts: - # Adding ts:// after http:// for some players - hostport = 'ts://' + hostport else: itemlist = header - for item in self.itemlist: + for i in self.itemlist: + item = i.copy() item['tvg'] = item.get('tvg', '') if item.get('tvg') else \ item.get('name').replace(' ', '_') # For .acelive and .torrent diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 5a98283..333939f 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -7,6 +7,8 @@ import urllib2 import time import gevent +import threading +import urlparse from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator import config.torrenttv as config @@ -17,69 +19,76 @@ class Torrenttv(AceProxyPlugin): # ttvplaylist handler is obsolete handlers = ('torrenttv', 'ttvplaylist') - logger = logging.getLogger('plugin_torrenttv') - playlist = None - playlisttime = None - def __init__(self, AceConfig, AceStuff): + self.logger = logging.getLogger('plugin_torrenttv') + self.lock = threading.Lock() + self.channels = None + self.playlist = None + self.playlisttime = None + if config.updateevery: - self.downloadPlaylist() gevent.spawn(self.playlistTimedDownloader) def playlistTimedDownloader(self): while True: + with self.lock: + self.downloadPlaylist() gevent.sleep(config.updateevery * 60) - self.downloadPlaylist() def downloadPlaylist(self): try: - Torrenttv.logger.debug('Trying to download playlist') + self.logger.debug('Trying to download playlist') req = urllib2.Request(config.url, headers={'User-Agent' : "Magic Browser"}) - Torrenttv.playlist = urllib2.urlopen( - req, timeout=10).read() - Torrenttv.playlisttime = int(time.time()) + origin = urllib2.urlopen(req, timeout=10).read() + matches = re.finditer(r',(?P\S.+) \((?P.+)\)\n(?P^.+$)', origin, re.MULTILINE) + + self.playlisttime = int(time.time()) + self.playlist = PlaylistGenerator() + self.channels = dict() + counter = 0 + + for match in matches: + counter += 1 + itemdict = match.groupdict() + name = itemdict.get('name').decode('UTF-8') + logo = config.logomap.get(name) + if logo is not None: + itemdict['logo'] = logo + self.playlist.addItem(itemdict) + self.channels[str(counter)] = itemdict['url'] + itemdict['url'] = str(counter) except: - Torrenttv.logger.error("Can't download playlist!") + self.logger.error("Can't download playlist!") return False return True def handle(self, connection, headers_only=False): - # 30 minutes cache - if not Torrenttv.playlist or (int(time.time()) - Torrenttv.playlisttime > 30 * 60): - if not self.downloadPlaylist(): - connection.dieWithError() - return - - hostport = connection.headers['Host'] - - connection.send_response(200) - connection.send_header('Content-Type', 'application/x-mpegurl') - connection.end_headers() - - if headers_only: - return + with self.lock: + + # 30 minutes cache + if not self.playlist or (int(time.time()) - self.playlisttime > 30 * 60): + if not self.downloadPlaylist(): + connection.dieWithError() + return + + url = urlparse.urlparse(connection.path) + + if url.path.endswith('/play'): + cid = urlparse.parse_qs(url.query)['id'][0] + connection.path = '/torrent/' + urllib2.quote(self.channels[cid], '') + '/stream.mp4' + connection.splittedpath = connection.path.split('/') + connection.reqtype = 'torrent' + connection.handleRequest(headers_only) + else: + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.end_headers() - # Match playlist with regexp - matches = re.finditer(r',(?P\S.+) \((?P.+)\)\n(?P^.+$)', - Torrenttv.playlist, re.MULTILINE) - - add_ts = False - try: - if connection.splittedpath[2].lower() == 'ts': - add_ts = True - except: - pass + if headers_only: + return - - playlistgen = PlaylistGenerator() - for match in matches: - itemdict = match.groupdict() - name = itemdict.get('name').decode('UTF-8') - logo = config.logomap.get(name) - if logo is not None: - itemdict['logo'] = logo - playlistgen.addItem(itemdict) - - header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) - connection.wfile.write(playlistgen.exportm3u(hostport, add_ts=add_ts, header=header)) + hostport = connection.headers['Host'] + '/torrenttv' + add_ts = True if url.path.endswith('/ts') else False + header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) + connection.wfile.write(self.playlist.exportm3u(hostport, add_ts=add_ts, header=header)) From ee5134d1fefeef38a67801112ce8507645b9add6 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 14 Mar 2016 22:18:49 +0300 Subject: [PATCH 16/95] Prefer WEB API since it's faster and more stable --- acehttp.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/acehttp.py b/acehttp.py index 6c4a285..2ec1c31 100755 --- a/acehttp.py +++ b/acehttp.py @@ -393,20 +393,20 @@ def getCid(self, reqtype, url): if not AceStuff.clientcounter.idleace: AceStuff.clientcounter.idleace = AceStuff.clientcounter.createAce() try: - cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) + req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) + f = base64.b64encode(urllib2.urlopen(req, timeout=5).read()) + req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) + req.add_header('Content-Type', 'application/octet-stream') + cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] except: pass if cid == '': try: - logging.debug("Failed to get CID from engine. Trying WEB API.") - req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) - f = urllib2.urlopen(req, timeout=3).read() - req = urllib2.Request('http://api.torrentstream.net/upload/raw', base64.b64encode(f)) - req.add_header('Content-Type', 'application/octet-stream') - cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] - except: logging.debug("Failed to get CID from WEB API") + cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) + except: + logging.debug("Failed to get CID from engine") return None if not cid or cid == '' else cid From 6691c0caaeee3ae142208864931ede6e3dcaf854 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 15 Mar 2016 01:02:27 +0300 Subject: [PATCH 17/95] Fixes --- aceclient/clientcounter.py | 8 ++++++-- plugins/torrenttv_plugin.py | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index c0bfa85..ae59f2e 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -23,7 +23,6 @@ def getClients(self, cid): def add(self, cid, client): with self.lock: clients = self.clients.get(cid) - self.total += 1 if clients: client.ace = clients[0].ace @@ -35,11 +34,16 @@ def add(self, cid, client): client.ace = self.idleace self.idleace = None else: - client.ace = self.createAce() + try: + client.ace = self.createAce() + except Exception as e: + logging.error('Failed to create AceClient: ' + repr(e)) + raise e clients = [client] self.clients[cid] = clients + self.total += 1 return len(clients) def delete(self, cid, client): diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 333939f..367ee86 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -52,11 +52,15 @@ def downloadPlaylist(self): itemdict = match.groupdict() name = itemdict.get('name').decode('UTF-8') logo = config.logomap.get(name) - if logo is not None: - itemdict['logo'] = logo + url = itemdict['url'] self.playlist.addItem(itemdict) - self.channels[str(counter)] = itemdict['url'] - itemdict['url'] = str(counter) + + if logo: + itemdict['logo'] = logo + + if url.startswith('http://') and url.endswith('.acelive'): + self.channels[str(counter)] = url + itemdict['url'] = str(counter) except: self.logger.error("Can't download playlist!") return False @@ -88,7 +92,11 @@ def handle(self, connection, headers_only=False): if headers_only: return - hostport = connection.headers['Host'] + '/torrenttv' + if len(self.channels) == 0: + hostport = connection.headers['Host'] + else: + hostport = connection.headers['Host'] + '/torrenttv' + add_ts = True if url.path.endswith('/ts') else False header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) connection.wfile.write(self.playlist.exportm3u(hostport, add_ts=add_ts, header=header)) From 4e313a99082fb0a1fed56f2c44aafcd6c443dea3 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 15 Mar 2016 20:04:12 +0300 Subject: [PATCH 18/95] Fixed VLC broadcasting --- aceclient/clientcounter.py | 7 +++++-- acehttp.py | 7 ++++++- vlcclient/vlcclient.py | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index ae59f2e..5bb42bc 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -49,16 +49,17 @@ def add(self, cid, client): def delete(self, cid, client): with self.lock: if not self.clients.has_key(cid): - return + return 0 clients = self.clients[cid] if client not in clients: - return + return len(clients) try: if len(clients) > 1: clients.remove(client) + return len(clients) else: del self.clients[cid] clients[0].ace.closeStreamReader() @@ -72,6 +73,8 @@ def delete(self, cid, client): self.idleace.reset() except: client.ace.destroy() + + return 0 finally: self.total -= 1 diff --git a/acehttp.py b/acehttp.py index 2ec1c31..faaaf66 100755 --- a/acehttp.py +++ b/acehttp.py @@ -375,10 +375,15 @@ def handleRequest(self, headers_only): self.dieWithError() finally: try: - AceStuff.clientcounter.delete(cid, self.client) + remaining = AceStuff.clientcounter.delete(cid, self.client) self.client.destroy() self.ace = None self.client = None + if AceConfig.vlcuse and remaining == 0: + try: + AceStuff.vlcclient.stopBroadcast(self.vlcid) + except: + pass logger.debug("END REQUEST") except: logger.error(traceback.format_exc()) diff --git a/vlcclient/vlcclient.py b/vlcclient/vlcclient.py index bf2be49..a084dc9 100644 --- a/vlcclient/vlcclient.py +++ b/vlcclient/vlcclient.py @@ -104,7 +104,7 @@ def _write(self, message): try: # Write message - self._socket.write(message + "\r\n") + self._socket.write(message.encode("UTF8") + "\r\n") except EOFError as e: raise VlcException("Vlc Write error! ERROR: " + repr(e)) From 94b9c71f8ee45683794bc750a83484ba312dbc52 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 16 Mar 2016 23:38:08 +0300 Subject: [PATCH 19/95] Use gevent.spawn instead of Timer --- aceclient/clientcounter.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index 5bb42bc..6ebb08c 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -5,6 +5,7 @@ import logging import time import aceclient +import gevent from aceconfig import AceConfig class ClientCounter(object): @@ -14,7 +15,7 @@ def __init__(self): self.clients = dict() self.idleace = None self.total = 0 - threading.Timer(60.0, self.checkIdle).start() + gevent.spawn(self.checkIdle) def getClients(self, cid): with self.lock: @@ -105,7 +106,6 @@ def deleteAll(self, cid): for c in clients: c.destroy() - def createAce(self): logger = logging.getLogger('createAce') ace = aceclient.AceClient( @@ -120,10 +120,11 @@ def createAce(self): return ace def checkIdle(self): - with self.lock: - ace = self.idleace - if ace and (ace._idleSince + 5.0 >= time.time()): - self.idleace = None - ace.destroy() + while(True): + gevent.sleep(60.0) + with self.lock: + ace = self.idleace + if ace and (ace._idleSince + 60.0 >= time.time()): + self.idleace = None + ace.destroy() - threading.Timer(60.0, self.checkIdle).start() From 6084802b7202f02dfe0e4c05a7f6cc45ee7a1a6c Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 16 Mar 2016 23:39:34 +0300 Subject: [PATCH 20/95] Encode vlc url --- acehttp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acehttp.py b/acehttp.py index faaaf66..312b969 100755 --- a/acehttp.py +++ b/acehttp.py @@ -27,6 +27,7 @@ import json import time import threading +import urllib import urllib2 import Queue import aceclient @@ -262,7 +263,7 @@ def handleRequest(self, headers_only): cid = contentid if contentid else self.path_unquoted logger.debug("CID: " + cid) self.client = Client(cid, self) - self.vlcid = cid + self.vlcid = urllib.quote_plus(cid) shouldStart = AceStuff.clientcounter.add(cid, self.client) == 1 # Send fake headers if this User-Agent is in fakeheaderuas tuple From dd4b767d9f0f5111f1a6ad80f8b903d1b7e73d49 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 17 Mar 2016 00:20:42 +0300 Subject: [PATCH 21/95] Implemented ETag support. Fixes --- aceclient/clientcounter.py | 2 +- plugins/torrenttv_plugin.py | 47 +++++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index 6ebb08c..532fd8a 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -124,7 +124,7 @@ def checkIdle(self): gevent.sleep(60.0) with self.lock: ace = self.idleace - if ace and (ace._idleSince + 60.0 >= time.time()): + if ace and (ace._idleSince + 60.0 <= time.time()): self.idleace = None ace.destroy() diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 367ee86..421433a 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -9,6 +9,8 @@ import gevent import threading import urlparse +import md5 +import traceback from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator import config.torrenttv as config @@ -25,6 +27,7 @@ def __init__(self, AceConfig, AceStuff): self.channels = None self.playlist = None self.playlisttime = None + self.etag = None if config.updateevery: gevent.spawn(self.playlistTimedDownloader) @@ -41,10 +44,10 @@ def downloadPlaylist(self): req = urllib2.Request(config.url, headers={'User-Agent' : "Magic Browser"}) origin = urllib2.urlopen(req, timeout=10).read() matches = re.finditer(r',(?P\S.+) \((?P.+)\)\n(?P^.+$)', origin, re.MULTILINE) - self.playlisttime = int(time.time()) self.playlist = PlaylistGenerator() self.channels = dict() + m = md5.new() counter = 0 for match in matches: @@ -59,15 +62,24 @@ def downloadPlaylist(self): itemdict['logo'] = logo if url.startswith('http://') and url.endswith('.acelive'): - self.channels[str(counter)] = url - itemdict['url'] = str(counter) + num = str(counter) + self.channels[num] = url + itemdict['url'] = num + + m.update(itemdict.get('name')) + m.update(itemdict['url']) + + self.etag = '"' + m.hexdigest() + '"' except: self.logger.error("Can't download playlist!") + self.logger.error(traceback.format_exc()) return False return True def handle(self, connection, headers_only=False): + play = False + with self.lock: # 30 minutes cache @@ -83,15 +95,14 @@ def handle(self, connection, headers_only=False): connection.path = '/torrent/' + urllib2.quote(self.channels[cid], '') + '/stream.mp4' connection.splittedpath = connection.path.split('/') connection.reqtype = 'torrent' - connection.handleRequest(headers_only) - else: - connection.send_response(200) - connection.send_header('Content-Type', 'application/x-mpegurl') + play = True + elif self.etag == connection.headers.get('If-None-Match'): + self.logger.debug('ETag matches - returning 304') + connection.send_response(304) + connection.send_header('Connection', 'close') connection.end_headers() - - if headers_only: - return - + return + else: if len(self.channels) == 0: hostport = connection.headers['Host'] else: @@ -99,4 +110,16 @@ def handle(self, connection, headers_only=False): add_ts = True if url.path.endswith('/ts') else False header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) - connection.wfile.write(self.playlist.exportm3u(hostport, add_ts=add_ts, header=header)) + exported = self.playlist.exportm3u(hostport, add_ts=add_ts, header=header) + + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.send_header('ETag', self.etag) + connection.send_header('Content-Length', str(len(exported))) + connection.send_header('Connection', 'close') + connection.end_headers() + + if play: + connection.handleRequest(headers_only) + elif not headers_only: + connection.wfile.write(exported) From 15946921170724734b13e2cf6bc2240cf91ce621 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 17 Mar 2016 01:23:06 +0300 Subject: [PATCH 22/95] Stat plugin enhancements --- acehttp.py | 8 +++++--- plugins/stat_plugin.py | 15 +++++++++++---- plugins/torrenttv_plugin.py | 7 ++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/acehttp.py b/acehttp.py index 312b969..155004c 100755 --- a/acehttp.py +++ b/acehttp.py @@ -214,7 +214,7 @@ def do_GET(self, headers_only=False): return self.handleRequest(headers_only) - def handleRequest(self, headers_only): + def handleRequest(self, headers_only, channelName=None, channelIcon=None): logger = logging.getLogger('handleRequest') # Check if third parameter exists @@ -262,7 +262,7 @@ def handleRequest(self, headers_only): contentid = self.getCid(self.reqtype, self.path_unquoted) cid = contentid if contentid else self.path_unquoted logger.debug("CID: " + cid) - self.client = Client(cid, self) + self.client = Client(cid, self, channelName, channelIcon) self.vlcid = urllib.quote_plus(cid) shouldStart = AceStuff.clientcounter.add(cid, self.client) == 1 @@ -426,9 +426,11 @@ def handle_error(self, request, client_address): class Client: - def __init__(self, cid, handler): + def __init__(self, cid, handler, channelName, channelIcon): self.cid = cid self.handler = handler + self.channelName=channelName + self.channelIcon=channelIcon self.ace = None self.lock = threading.Condition(threading.Lock()) self.queue = deque() diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index 9a42c07..ac96cc2 100644 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -7,7 +7,7 @@ class Stat(AceProxyPlugin): - handlers = ('stat', ) + handlers = ('stat',) def __init__(self, AceConfig, AceStuff): self.config = AceConfig @@ -24,8 +24,15 @@ def handle(self, connection, headers_only=False): connection.wfile.write( '

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') connection.wfile.write( - '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') + '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') for i in self.stuff.clientcounter.clients: for c in self.stuff.clientcounter.clients[i]: - connection.wfile.write(c.handler.clientip + ': ' + c.handler.path_unquoted + '
') - connection.wfile.write('') + connection.wfile.write('') + connection.wfile.write('
') + if c.channelIcon: + connection.wfile.write(' ') + if c.channelName: + connection.wfile.write(c.channelName.encode('UTF8')) + else: + connection.wfile.write(i) + connection.wfile.write('' + c.handler.clientip + '
') diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 421433a..38a8e1e 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -63,7 +63,7 @@ def downloadPlaylist(self): if url.startswith('http://') and url.endswith('.acelive'): num = str(counter) - self.channels[num] = url + self.channels[num] = [name, url] itemdict['url'] = num m.update(itemdict.get('name')) @@ -92,7 +92,8 @@ def handle(self, connection, headers_only=False): if url.path.endswith('/play'): cid = urlparse.parse_qs(url.query)['id'][0] - connection.path = '/torrent/' + urllib2.quote(self.channels[cid], '') + '/stream.mp4' + channel = self.channels[cid] + connection.path = '/torrent/' + urllib2.quote(channel[1], '') + '/stream.mp4' connection.splittedpath = connection.path.split('/') connection.reqtype = 'torrent' play = True @@ -120,6 +121,6 @@ def handle(self, connection, headers_only=False): connection.end_headers() if play: - connection.handleRequest(headers_only) + connection.handleRequest(headers_only, channel[0], config.logomap.get(channel[0])) elif not headers_only: connection.wfile.write(exported) From 6fa734b0804f7b78befc5dbde43f6d48d1789209 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 17 Mar 2016 02:24:57 +0300 Subject: [PATCH 23/95] Fixed archive --- acehttp.py | 38 +++++++++++++++++++------------------- plugins/torrenttv_api.py | 10 ++++++---- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/acehttp.py b/acehttp.py index 155004c..7deb0c5 100755 --- a/acehttp.py +++ b/acehttp.py @@ -391,28 +391,28 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None): def getCid(self, reqtype, url): cid = '' - + if reqtype == 'torrent': - if url[:4].lower() == 'http' : - if url[-8:].lower() == '.acelive' : - with AceStuff.clientcounter.lock: - if not AceStuff.clientcounter.idleace: - AceStuff.clientcounter.idleace = AceStuff.clientcounter.createAce() - try: - req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) - f = base64.b64encode(urllib2.urlopen(req, timeout=5).read()) - req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) - req.add_header('Content-Type', 'application/octet-stream') - cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] - except: - pass + if url.startswith('http'): + if url.endswith('.acelive') or url.endswith('.acestream'): + try: + req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) + f = base64.b64encode(urllib2.urlopen(req, timeout=5).read()) + req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) + req.add_header('Content-Type', 'application/octet-stream') + cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] + except: + pass - if cid == '': - try: - logging.debug("Failed to get CID from WEB API") + if cid == '': + logging.debug("Failed to get CID from WEB API") + try: + with AceStuff.clientcounter.lock: + if not AceStuff.clientcounter.idleace: + AceStuff.clientcounter.idleace = AceStuff.clientcounter.createAce() cid = AceStuff.clientcounter.idleace.GETCID(reqtype, url) - except: - logging.debug("Failed to get CID from engine") + except: + logging.debug("Failed to get CID from engine") return None if not cid or cid == '' else cid diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py index 2fb76d9..10547f9 100644 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -107,17 +107,19 @@ def records(self, channel_id, date, raw=False): :param raw: if True returns unprocessed data :return: records list """ + date = date.replace('-0', '-') + if raw: try: - res = self._xmlresult('arc_records.php', '&channel_id=' + channel_id + '&date=' + date) + res = self._xmlresult('arc_records.php', '&epg_id=' + channel_id + '&date=' + date) self._checkxml(res) return res except TorrentTvApiException: - res = self._xmlresult('arc_records.php', '&channel_id=' + channel_id + '&date=' + date) + res = self._xmlresult('arc_records.php', '&epg_id=' + channel_id + '&date=' + date) self._checkxml(res) return res else: - res = self._xmlresult('arc_records.php', '&channel_id=' + channel_id + '&date=' + date) + res = self._checkedxmlresult('arc_records.php', '&epg_id=' + channel_id + '&date=' + date) return res.getElementsByTagName('channel') def archive_channels(self, raw=False): @@ -139,7 +141,7 @@ def archive_channels(self, raw=False): self._checkxml(res) return res else: - res = self._xmlresult('arc_list.php', '') + res = self._checkedxmlresult('arc_list.php', '') return res.getElementsByTagName('channel') def stream_source(self, channel_id): From 5342c09e68439e2ff9508a50b04819f9a245e4d8 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 17 Mar 2016 20:37:04 +0300 Subject: [PATCH 24/95] #EXTGRP removed from the default template since it's not supported by some players --- plugins/config/playlist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py index 9dbf6ca..481a945 100644 --- a/plugins/config/playlist.py +++ b/plugins/config/playlist.py @@ -3,5 +3,6 @@ m3uemptyheader = '#EXTM3U\n' m3uheader = \ '#EXTM3U url-tvg="http://1ttvapi.top/ttv.xmltv.xml.gz"\n' +# If you need the #EXTGRP field put this #EXTGRP:%(group)s\n after %(name)s\n. m3uchanneltemplate = \ - '#EXTINF:-1 group-title="%(group)s" tvg-name="%(tvg)s" tvg-id="%(tvgid)s" tvg-logo="%(logo)s",%(name)s\n#EXTGRP:%(group)s\n%(url)s\n' + '#EXTINF:-1 group-title="%(group)s" tvg-name="%(tvg)s" tvg-id="%(tvgid)s" tvg-logo="%(logo)s",%(name)s\n%(url)s\n' From 6cb25827cc9dadcb8645ff0c24a902b00e39edb7 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 17 Mar 2016 20:40:16 +0300 Subject: [PATCH 25/95] Use channel name instead of sequence number. Fixes. --- acehttp.py | 3 +-- plugins/modules/PlaylistGenerator.py | 35 +++++++++++++++------------- plugins/torrenttv_plugin.py | 32 +++++++++++-------------- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/acehttp.py b/acehttp.py index 7deb0c5..053730b 100755 --- a/acehttp.py +++ b/acehttp.py @@ -27,7 +27,6 @@ import json import time import threading -import urllib import urllib2 import Queue import aceclient @@ -263,7 +262,7 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None): cid = contentid if contentid else self.path_unquoted logger.debug("CID: " + cid) self.client = Client(cid, self, channelName, channelIcon) - self.vlcid = urllib.quote_plus(cid) + self.vlcid = urllib2.quote(cid, '') shouldStart = AceStuff.clientcounter.add(cid, self.client) == 1 # Send fake headers if this User-Agent is in fakeheaderuas tuple diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py index 9a393ba..4e5041a 100644 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -41,7 +41,7 @@ def _generatem3uline(item): return config.m3uchanneltemplate % item - def exportm3u(self, hostport, add_ts=False, empty_header=False, archive=False, header=None): + def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, header=None): ''' Exports m3u playlist ''' @@ -60,23 +60,26 @@ def exportm3u(self, hostport, add_ts=False, empty_header=False, archive=False, h for i in self.itemlist: item = i.copy() - item['tvg'] = item.get('tvg', '') if item.get('tvg') else \ - item.get('name').replace(' ', '_') + item['name'] = item['name'].replace('"', "'").replace(',', '.') + item['tvg'] = item.get('tvg', '') if item.has_key('tvg') else item.get('name').replace(' ', '_') + url = item['url']; + # For .acelive and .torrent - item['url'] = re.sub('^(http.+)$', lambda match: 'http://' + hostport + '/torrent/' + \ - urllib2.quote(match.group(0), '') + '/stream.mp4', item['url'], + item['url'] = re.sub('^(http.+)$', lambda match: 'http://' + hostport + path + '/torrent/' + \ + urllib2.quote(match.group(0), '') + '/stream.mp4', url, flags=re.MULTILINE) - # For PIDs - item['url'] = re.sub('^(acestream://)?(?P[0-9a-f]{40})$', 'http://' + hostport + '/pid/\\g/stream.mp4', - item['url'], flags=re.MULTILINE) - - # For channel id's - if archive: - item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + '/archive/play?id=' + match.group(0), - item['url'], flags=re.MULTILINE) - else: - item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + '/channels/play?id=' + match.group(0), - item['url'], flags=re.MULTILINE) + if url == item['url']: # For PIDs + item['url'] = re.sub('^(acestream://)?(?P[0-9a-f]{40})$', 'http://' + hostport + path + '/pid/\\g/stream.mp4', + url, flags=re.MULTILINE) + if archive and url == item['url']: # For archive channel id's + item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/archive/play?id=' + match.group(0), + url, flags=re.MULTILINE) + if not archive and url == item['url']: # For channel id's + item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/channels/play?id=' + match.group(0), + url, flags=re.MULTILINE) + if url == item['url']: # For channel names + item['url'] = re.sub('^([^/]+)$', lambda match: 'http://' + hostport + path + '/' + match.group(0), + url, flags=re.MULTILINE) itemlist += PlaylistGenerator._generatem3uline(item) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 38a8e1e..5171715 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -48,12 +48,11 @@ def downloadPlaylist(self): self.playlist = PlaylistGenerator() self.channels = dict() m = md5.new() - counter = 0 for match in matches: - counter += 1 itemdict = match.groupdict() - name = itemdict.get('name').decode('UTF-8') + encname = itemdict.get('name') + name = encname.decode('UTF-8') logo = config.logomap.get(name) url = itemdict['url'] self.playlist.addItem(itemdict) @@ -62,12 +61,10 @@ def downloadPlaylist(self): itemdict['logo'] = logo if url.startswith('http://') and url.endswith('.acelive'): - num = str(counter) - self.channels[num] = [name, url] - itemdict['url'] = num + self.channels[name] = url + itemdict['url'] = urllib2.quote(encname, '') - m.update(itemdict.get('name')) - m.update(itemdict['url']) + m.update(encname) self.etag = '"' + m.hexdigest() + '"' except: @@ -90,10 +87,10 @@ def handle(self, connection, headers_only=False): url = urlparse.urlparse(connection.path) - if url.path.endswith('/play'): - cid = urlparse.parse_qs(url.query)['id'][0] - channel = self.channels[cid] - connection.path = '/torrent/' + urllib2.quote(channel[1], '') + '/stream.mp4' + if url.path.startswith('/torrenttv/channel/'): + name = urllib2.unquote(url.path[19:]).decode('UTF8') + url = self.channels[name] + connection.path = '/torrent/' + urllib2.quote(url, '') + '/stream.mp4' connection.splittedpath = connection.path.split('/') connection.reqtype = 'torrent' play = True @@ -104,14 +101,11 @@ def handle(self, connection, headers_only=False): connection.end_headers() return else: - if len(self.channels) == 0: - hostport = connection.headers['Host'] - else: - hostport = connection.headers['Host'] + '/torrenttv' - + hostport = connection.headers['Host'] + path = '' if len(self.channels) == 0 else '/torrenttv/channel' add_ts = True if url.path.endswith('/ts') else False header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) - exported = self.playlist.exportm3u(hostport, add_ts=add_ts, header=header) + exported = self.playlist.exportm3u(hostport, path, add_ts=add_ts, header=header) connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') @@ -121,6 +115,6 @@ def handle(self, connection, headers_only=False): connection.end_headers() if play: - connection.handleRequest(headers_only, channel[0], config.logomap.get(channel[0])) + connection.handleRequest(headers_only, name, config.logomap.get(name)) elif not headers_only: connection.wfile.write(exported) From a3071d074d245ed4cb3247d8b027e7bef0e2d7d7 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 17 Mar 2016 20:41:20 +0300 Subject: [PATCH 26/95] Added charset to the Content-type header --- plugins/stat_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index ac96cc2..82a8543 100644 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -15,7 +15,7 @@ def __init__(self, AceConfig, AceStuff): def handle(self, connection, headers_only=False): connection.send_response(200) - connection.send_header('Content-type', 'text/html') + connection.send_header('Content-type', 'text/html; charset=utf-8') connection.end_headers() if headers_only: From bf51dfc1e81a806985645090acf9dbf77d5f9fff Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 17 Mar 2016 20:43:46 +0300 Subject: [PATCH 27/95] Implemented archive m3u. --- plugins/p2pproxy_plugin.py | 74 ++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index e5cc302..027f738 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -30,8 +30,9 @@ class P2pproxy(AceProxyPlugin): + TTV = 'http://torrent-tv.ru/' + TTVU = TTV + 'uploads/' handlers = ('channels', 'archive', 'xbmc.pvr', 'logos') - logger = logging.getLogger('plugin_p2pproxy') def __init__(self, AceConfig, AceStuff): @@ -116,7 +117,7 @@ def handle(self, connection, headers_only=False): cid = channel.getAttribute('id') logo = channel.getAttribute('logo') if config.fullpathlogo: - logo = 'http://torrent-tv.ru/uploads/' + logo + logo = P2pproxy.TTVU + logo fields = {'name': name, 'id': cid, 'url': cid, 'group': group, 'logo': logo} fields['tvgid'] = config.tvgid %fields @@ -225,40 +226,58 @@ def handle(self, connection, headers_only=False): connection.dieWithError() return - param_channel = self.get_param('channel_id') - if param_channel == '' or not param_channel: - P2pproxy.logger.error('Got /archive/ request but no channel_id specified!') - connection.dieWithError() - return - if headers_only: connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') connection.end_headers() return - records_list = self.api.records(param_channel, d.strftime('%d-%m-%Y')) - channels_list = self.api.archive_channels() playlistgen = PlaylistGenerator() - P2pproxy.logger.debug('Generating archive m3u playlist') + param_channel = self.get_param('channel_id') + d = d.strftime('%d-%m-%Y').replace('-0', '-') - for record in records_list: - record_id = record.getAttribute('record_id') - name = record.getAttribute('name') - channel_id = record.getAttribute('channel_id') - channel_name = '' - logo = '' + if param_channel == '' or not param_channel: + channels_list = self.api.archive_channels() + for channel in channels_list: - if channel.getAttribute('channel_id') == channel_id: - channel_name = channel.getAttribute('name') - logo = channel.getAttribute('logo') - - if channel_name != '': - name = '(' + channel_name + ') ' + name - if logo != '' and config.fullpathlogo: - logo = 'http://torrent-tv.ru/uploads/' + logo + channel_id = channel.getAttribute('epg_id') + try: + records_list = self.api.records(channel_id, d) + channel_name = channel.getAttribute('name') + logo = channel.getAttribute('logo') + if logo != '' and config.fullpathlogo: + logo = P2pproxy.TTVU + logo + + for record in records_list: + name = record.getAttribute('name') + record_id = record.getAttribute('record_id') + playlistgen.addItem({'group': channel_name, 'tvg': '', + 'name': name, 'url': record_id, 'logo': logo}) + except: + P2pproxy.logger.debug('Failed to load archive for ' + channel_id) - playlistgen.addItem({'name': name, 'url': record_id, 'logo': logo}) + else: + records_list = self.api.records(param_channel, d) + channels_list = self.api.archive_channels() + P2pproxy.logger.debug('Generating archive m3u playlist') + + for record in records_list: + record_id = record.getAttribute('record_id') + name = record.getAttribute('name') + channel_id = record.getAttribute('channel_id') + channel_name = '' + logo = '' + for channel in channels_list: + if channel.getAttribute('channel_id') == channel_id: + channel_name = channel.getAttribute('name') + logo = channel.getAttribute('logo') + + if channel_name != '': + name = '(' + channel_name + ') ' + name + if logo != '' and config.fullpathlogo: + logo = P2pproxy.TTVU + logo + + playlistgen.addItem({'name': name, 'url': record_id, 'logo': logo}) P2pproxy.logger.debug('Exporting') exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True) @@ -266,6 +285,7 @@ def handle(self, connection, headers_only=False): connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') + connection.send_header('Content-Length', str(len(exported))) connection.end_headers() connection.wfile.write(exported) else: # /archive/?date=[param_date]&channel_id=[param_channel] @@ -306,7 +326,7 @@ def handle(self, connection, headers_only=False): connection.send_response(200) connection.send_header('Content-Type', 'text/plain;charset=utf-8') connection.end_headers() - connection.wfile.write("logobase = 'http://torrent-tv.ru/uploads/'\n") + connection.wfile.write("logobase = '" + P2pproxy.TTVU +"'\n") connection.wfile.write("logomap = {\n") for channel in translations_list: From 2c93fee2c6b797bc265e90b58d03263e7ce99d40 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 18 Mar 2016 20:44:42 +0300 Subject: [PATCH 28/95] Updated logos --- plugins/config/torrenttv.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 7f52c98..5b67137 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -40,7 +40,6 @@ u'2x2 (+5)': logobase + 'WMlvhNmhzmk2JgKct8Oa5FcWHmR7fZ.png', u'360 градусов': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', u'360 градусов HD': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', - u'365 Music HD': logobase + 'qptiMVru1g26wTj9ZrfLBhbeeVBIB5.png', u'365 Дней': logobase + '0IZrVwoxtmjtgnWu5Dj4Hb8FRc8NIX.png', u'3S TV': logobase + 'AL7uQOgsyYqE7V1BagxC2jqQVOvucp.png', u'43 Канал HD': logobase + '1mNoU1QXmcmK48eVTlnADtsEBmA2sN.png', @@ -69,7 +68,6 @@ u'Az TV': logobase + 'FB2qxgNm8xTU6YiR2fY1jH4PIRR2SF.png', u'B4U Music UK': logobase + 'LlfDsq3FEU2D4P3lmj25fvl6zQnvNM.png', u'BabyTV': logobase + 'BXLto7eBgsRpbGP6Cb8tgIfh4h5RiK.png', - u'Balticum Auksinis': logobase + 'dJVlqa8Uy1m75CUODkV70yN5P9GFjt.png', u'Balticum Platinum': logobase + 'c0hXvCfIFAs4Nt4SSGmfZ5DMZYg4jJ.png', u'BBC One': logobase + 'ofuDCyM7MgzwFbzG2pyavFu6xeRHwp.png', u'BBC Two': logobase + 'o7LyIuTJT2C7wJG77Zx5cZhBYP2L8B.png', @@ -81,10 +79,14 @@ u'Brazzers TV Europe': logobase + 'OmVBp3Kz4Lx722dq2e1OxE26QSxrDA.png', u'Bridge TV': logobase + 'dPhBiaViIznwjeWU3pTbIXFmS3iJkU.png', u'Brodilo TV HD': logobase + '0pLPWiGqZjDe2O4vbPd8QL0qGsA9lq.png', + u'BT Sport 1': logobase + 'RNwudSF1Lys88WmIXhiS43g2urfknl.png', + u'BT Sport 2': logobase + 'NdEFtAJBS9EtR2rSEUZ1ZF15tQzH6y.png', u'BTV': logobase + '52ZaTijyyDOsKI7B1fYnUWliW6i2ah.png', u'Business': logobase + 'wvwXc2x8Bjev90GD6LO6t7TL2aKW1h.png', u'C More Tennis': logobase + 'ql4qy7gHN4GtWhcqfd97HqXUs8HXI9.png', u'C Music TV HD': logobase + 'YhtkJhsV8NKwm4FGWhQZDmYcW0TYQS.png', + u'Canal Fox ES': logobase + 'JiHKq6sUTY5tfS8NC2HtHmKgYgYHg3.png', + u'Canal TNT ES': logobase + 'Lk4pqg1KwHSEEutL9jQ1sTautSVtRB.png', u'Canal+ Deportes': logobase + 'qoi5H4OujPPtCC1JJO1ozeyFLPqPk6.png', u'Canal+ Deportes 2': logobase + '4EUoskZorTXBkMxeR8ykSpyQOErvgu.png', u'Canal+ Futbol': logobase + '1En2KRyo8ywbMPWjiiri7TcEXJbqcE.png', @@ -101,6 +103,9 @@ u'Chasse et Peche': logobase + '0X53uW9WfdUDtISMaT71SaPSf0rv3C.png', u'CNL': logobase + 'wwQ6u4lFXyaUDJLu2JOh6mtbkIz0Nf.png', u'CNN International': logobase + 'lIwbRbM3ve4ixhFXp75Oap9nzcO71G.png', + u'CT 1': logobase + 'sgPGofRHRzOlNb6dBsk5QGiCdzJewS.png', + u'CT 2': logobase + 'dFDWTyqFwS3roF7Jb3JGZIfm1htuUv.png', + u'CT 24': logobase + 'nylXA3Qw7fJt1ult4NUg7vTtpZobZz.png', u'Da Vinci Learning': logobase + 'Yl6p1IDDkZxxiUa3p2JxI66mIlOPns.png', u'Dange TV': logobase + 'ofmgiZlRmwOZtTaYtUtfErZS3ODDDM.png', u'Deluxe Music': logobase + '3VnQoAyJh3RZM88USPszL1TZIE5t0u.png', @@ -132,14 +137,11 @@ u'EuroNews': logobase + 'Vb3fP5gUK0q40WuzYeUhMT7RQmDg27.png', u'Europa Plus TV': logobase + 'PkatgpdmA4ArsgSG5shu0ZCQQ5RgMx.png', u'Eurosport 1': logobase + 'QA9jgUaQRrE4vMno04eM3aUrklXOce.png', - u'Eurosport 1 British': logobase + 'sKWbooLWxKfwjge6dMOTWi35gQvcG8.png', u'Eurosport 1 HD': logobase + 'DpFTzUEA3y67Z6ObTPF4xH0XLNRAZm.png', - u'Eurosport 2 British': logobase + 'syqFtqcYUfxX6t2wcfSGKNbKQ7vGgN.png', u'Eurospоrt 2': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', u'Eurospоrt 2 HD': logobase + '0tv5hm546AKIySs7cpj30LlCOcY0Mj.png', u'Exotica TV': logobase + 'K5hm3mURkkDaSc7RI5RDti5edynMGl.png', u'Extreme Sports': logobase + '21FhIqWK82JDPNuLTEIC9hSO2EHfks.png', - u'F+ BG': logobase + 'DJZlMhUwCNcVypOBoytE7QQnKtMepM.png', u'Fashion One HD': logobase + 'iPs2ptiBXm8h0KSnRmyqu45texHNig.png', u'Fashion TV': logobase + 'PSjqabjhYIBqcS8hUA8WNrZEjV4zZY.png', u'Fine Living': logobase + '22I1fK1aMCUgeYUCV0K3vwmlJKELZD.png', @@ -167,7 +169,6 @@ u'Hit TV': logobase + 'FnCnW1vmvm8gjAwMGXe2Q8qtN1C3zy.png', u'Hustler HD': logobase + 'LBz8ia8AASewVuLjMs5v4MDiVYfsJO.png', u'Hustler TV': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', - u'Hustler TV Europe': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', u'iConcerts HD': logobase + 'fLNDK6Nxz7xMv61nOsZvED749DlOtz.png', u'ICTV': logobase + 'YuNtYxhj9vqgTU9kVuz9imhqviY4PZ.png', u'ID Fashion HD': logobase + 'v5Ahro1G6KQVugydvnyZbHjhM6BxhR.png', @@ -206,6 +207,7 @@ u'Music Box TV': logobase + 'fvt4pris0lwnVhSyUrh8QlyzWBhbgz.png', u'Music Box UA': logobase + '8T7Rnct8q2VHhRm0BcGFxCjxuYEArc.png', u'Music Box UK': logobase + 'KUbeGDe2HthXSDa8OqMhia0nhTSL61.png', + u'Music InTV': logobase + 'QTJnJokXeUpNksMV5cp0TiE9lyUesQ.png', u'Mute TV 3D': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', u'Mute TV HD': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', u'MyZen TV HD': logobase + 'Nm18g9xkyhx73F2vxLCPYVRwX6k0H6.png', @@ -220,7 +222,6 @@ u'Nickelodeon': logobase + 'j66xpaZbfiYIgQxv76QAPckPVjmLNs.png', u'Nickelodeon HD': logobase + 'CFYx7Bd1aDSgkqjMLLQjFE6xe3u1E0.png', u'Nova Sport': logobase + '8npwMe6SAEgj5nMBCoVPCemX9fKOvI.png', - u'Nova Sport BG': logobase + 'BZNQubMesUUPrPyQdhdUEICSjkoaXf.png', u'Nuart TV': logobase + 'aqMIuUixqLQYmJITPnOGtFkRPuTqKa.png', u'O-la-la': logobase + '7ddvb8Tivq7yuEgffrKildHi2BQcCE.png', u'O-TV': logobase + 'UlIt4rE3FZs7IQmPN3tsGS6fqY5F1D.png', @@ -238,8 +239,9 @@ u'Poker Central HD': logobase + 'XfvAn9FaPBiRrhXBluK2kUPqxSftAo.png', u'Polonia 1': logobase + '3fzUyCBgUoJ6zqc92KSXh9g9utJEuO.png', u'Polsat Sport Extra HD': logobase + 'AdIBJziuh63ffasA6KeDMAKV9DPBqL.png', + u'Polsat Sport HD': logobase + 'zAC1zjpeHrGa3vb5nPLvvNJ8SooQes.png', + u'Polsat Sport News': logobase + 'WhGmGGlQXEvepTfPBd3u1to4ekbtDN.png', u'Premier Sports': logobase + 'dIKsh48t49lFlDcKPhHKsvYsKaOfNS.png', - u'Private TV': logobase + 'fAju50cZ4EUz2friirWrJgWfkY4R7s.png', u'Pro Все': logobase + 'OavKhOpBMn27qPDKJXXm5rgATgovgn.png', u'QTV': logobase + 'zmh1xStjBZGJ6U5g3xLoemD2oT3w3h.png', u'R1': logobase + 'OducDuiwKukJTHXoQo49vuh62PvXK2.png', @@ -261,8 +263,6 @@ u'SCT': logobase + 'bDRN0guXHaNHruxtyFkpKYz8J09Xj3.png', u'SET': logobase + 'tKzOKIcOQYrFl1VL8B0QqERFXCAYfU.png', u'SET HD': logobase + 'sX1unYoKj8JR7m8lbtkmPCClRrjAZ9.png', - u'Setanta Sports + HD': logobase + 'jW7pJhmebW2fZsXUTvDuRQLahgiLlU.png', - u'Setanta Sports Eurasia': logobase + 'RMqd8QOE9eqzAv7pDstbAJlSAXL3RN.png', u'Setanta Sports Eurasia HD': logobase + '1wgHdJP76TCItF14FxDwBtak8tmxRv.png', u'Sextosenso': logobase + 'HXMvFMLO9weHUcolVmmMKpz98T0K4K.png', u'Shant TV': logobase + 'IgJpH1JzyjI55Ki2G2Ybz6jzOkwG0T.png', @@ -278,15 +278,13 @@ u'Sport 1 Cz': logobase + 'kCLlfkFz3Ba3BL9Jc9ZPgUKXh2piyv.png', u'Sport 1 LT': logobase + 'nRFc87aOV1vRnjEqmQZUneZe4HiCqn.png', u'Sport 2 Cz': logobase + 'YLmEjnczWQGJcZC0SxRcH4ifPcwYlx.png', + u'Sport Klub 1 HD': logobase + 'cLQ3uuWhQqxCQk5RUDwA9x7bLUHBwn.png', u'Sport TV 2': logobase + 'u6T8L5PPYKHCbBATjdzjLpTC8zzCdV.png', u'Sport TV 3': logobase + 'dYTM6Oqhaqw18FI6uYPS5yhjCmc1nZ.png', u'Sporta Centrs TV': logobase + 'FJky9xgBrm265yuoNH3K2OZTINMLPg.png', u'STV': logobase + 'DbKEKL5gUOFHiruYRjY2H9gTLOV5mu.png', - u'Super One HD (SD)': logobase + 'eSbbtO5TzmQ2gDBpeGsKrMG8bKbiG1.png', u'Super Tennis HD': logobase + 'mjQW91VJdjIEhADvOO2s6OiKNeUdUK.png', - u'Teledeporte': logobase + '57r0Kq1rFB6vcMeldfWDvp438Jz5qT.png', u'Teletravel HD': logobase + '4ZlASq3oDpOjXfhwluOzY74sy9elaE.png', - u'Ten HD': logobase + '2zGLzb2KRlh0RVlCReqyNantzttGGO.png', u'TGirls TV': logobase + 'FufZ2heFswzvAbRkTQZs8UJBYGsxuG.png', u'TiJi': logobase + 'mD3GW0E7rdPwc4stjk7xrLI2gZn4Hq.png', u'TLC': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', @@ -306,7 +304,6 @@ u'TV Safina': logobase + 'mJUmNhJbQqcr2NPppAryEJqDPBJGV0.png', u'TV Sale': logobase + 'hs0YdiUTlpRtb3wTiP4cXboX0H9oTN.png', u'TV XXI (TV21)': logobase + 'TKchoTWZFRMmGDBok08zoEFJ8mJJCe.png', - u'TV1 LT': logobase + 'CXD4Zq54oHMsByuJbWtdIqzD24dVVo.png', u'TV1000 Comedy HD': logobase + 'ygGiR2hkQLySH6khdo8GV9CyMJ8dXi.png', u'TV1000 Megahit HD': logobase + 'lVPY7WCjn1WM6NL6tfLFy8iGA4yk3Z.png', u'TV1000 Premium HD': logobase + 'raoDrpin8VKmi522LZWzSF0fLRO04m.png', @@ -322,9 +319,10 @@ u'VH1 Classic': logobase + 'FhxUFQ2Bsfom4vb8Ce41gFObAbh1Vh.png', u'Viasat Explore': logobase + 'uCqpsdKP0ialUUYxUk2fXshYdYfxzW.png', u'Viasat Fotboll': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', - u'Viasat Fotboll HD': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', + u'Viasat Golf': logobase + 'IGpQl5iTxaDEPyKffdDEpU0EU0SPiO.png', u'Viasat History': logobase + 'MWGbB8wJp5Gm4vbPHl0ktohDDjMKdr.png', u'Viasat Hockey': logobase + 'CuAbCRGdf3Z1FGFiwErTbHZ3lAMJzr.png', + u'Viasat Motor': logobase + 'RuYtGxEpqJ5DG7WxGCMWNDXosRdh59.png', u'Viasat Nature East': logobase + 'yimDcPvajJcUKQm9bY15cDdp3rJFcp.png', u'Viasat Nature-History HD': logobase + 'pSP6zxmuO4PU6xa6KRlZ9L8vvVM2Dy.png', u'Viasat Sport': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', @@ -356,6 +354,7 @@ u'Громадське ТБ HD': logobase + 'Ovkd9TiVv3nLcKPwQS2wkJ85KyYCMQ.png', u'Детский': logobase + 'jk8kody2p38CKdj5KGXWMwRLjgFIlG.png', u'Детский мир': logobase + '00Vf3rPABNnbNQ6Rv0dnfcg3JsJelA.png', + u'Джус ТВ': logobase + 'qVNFoyUAOJSDvN9tHhf9j2AP7x4VkV.png', u'Дождь': logobase + '381.png', u'Дождь HD': logobase + '381.png', u'Дом кино': logobase + 'jlC78Fy13KWjQUN6l3FtbsRLZDvc0x.png', @@ -389,6 +388,7 @@ u'История': logobase + 'PNRaeOUFzOPFtrclFBBRTckj6Lvo0u.png', u'К1': logobase + 'mk2mYb28HFIxkFIiMNQWmKUdn1Y8hD.png', u'К2': logobase + 'IjG76jf8k8HTNLooNpUiEXtkPfA2rG.png', + u'Карапуз ТВ HD': logobase + 'ieXzw5xeahW3OoO66m2LX8GwLCYyYl.png', u'Карусель': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'Карусель International': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'Киевская Русь': logobase + 'C1AZimW2NnNA17H1uJLxxePUMTPQZ7.png', @@ -396,7 +396,7 @@ u'Кинопоказ': logobase + 'v0JEbxExcFI8dVEzCkpZUoktgiS9t7.png', u'Кинопоказ 1 HD': logobase + 'pNA1vR3sPoovYNKzO4TU6NQcSXNqjk.png', u'Кинопоказ 2 HD': logobase + 'Yf2XKrtorOF2wSZ23Q9NHhL2MfOcqi.png', - u'Кинопремиум HD': logobase + 'X7n162e9CVIachbSlwXU2qtA1C7zz7.png', + u'КиноПремиум HD': logobase + 'p580CRZ8bBS6dw3plMWhhxXSzQ59uS.png', u'Комедия ТВ': logobase + 'L2MEpT2YePoDvmKRjYy6yyt5ssH1m4.png', u'Красная линия': logobase + 'I43S6jd5noclar0LlPJnyY8adonmUV.png', u'Кто есть кто': logobase + 'MwNkO3fXd6KefRdiGlOdOQ5q0Zu7kS.png', @@ -472,7 +472,6 @@ u'Оплот ТВ': logobase + 'gvofGxTug45qSt1vsX0BPzQxGTrwTr.png', u'Оружие': logobase + 'CyDUCmYXK8WS2kXCX5kiAOFejnlwoP.png', u'Остросюжетное HD': logobase + 'mxF7CZsqsDRMMK4pN8ekdccEgvEsZC.png', - u'ОТВ': logobase + 'tjw4uBPEbAHEkB75vTGHUsFtQ4nEKj.png', u'ОТР': logobase + 'CqxKorK72v3ULbWkB3ZOhdte0duYZa.png', u'Охота и рыбалка': logobase + '5l2P20J6ebTh0ptOr27Hh704niP3nU.png', u'Охотник и рыболов': logobase + 'Ws2ddPI0b5Ie7PymoPUsboVlz9lYMS.png', @@ -554,7 +553,6 @@ u'ТВ 3': logobase + '427.png', u'ТВ 3 (+3)': logobase + '427.png', u'ТВ 3 (+3)': logobase + 'YzjZZoc01zFHW2bm6CuP2jkHo0byV3.png', - u'ТВ2-ТВ': logobase + 'fFl6h6QUwFKoI3N4lqEyhNLdACGkNq.png', u'Тверской проспект': logobase + 'k6RPWpDIwfsOZ5qkqHF5ZdOf5GqfmH.png', u'ТВЦ': logobase + 'QEpQTskZ9hcfI0rgD8osHVYSv58pde.png', u'ТДК': logobase + 'eSrHE6Gws4U6JxhFXA3mQ4iDVc0SwS.png', From 85f6d29946f50888840caa82a6b50b72d43e6e8d Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 18 Mar 2016 20:46:22 +0300 Subject: [PATCH 29/95] Added .mp4 suffix to urls. --- plugins/torrenttv_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 5171715..2c41ed7 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -62,7 +62,7 @@ def downloadPlaylist(self): if url.startswith('http://') and url.endswith('.acelive'): self.channels[name] = url - itemdict['url'] = urllib2.quote(encname, '') + itemdict['url'] = urllib2.quote(encname, '') +'.mp4' m.update(encname) @@ -88,7 +88,7 @@ def handle(self, connection, headers_only=False): url = urlparse.urlparse(connection.path) if url.path.startswith('/torrenttv/channel/'): - name = urllib2.unquote(url.path[19:]).decode('UTF8') + name = urllib2.unquote(url.path[19:-4]).decode('UTF8') url = self.channels[name] connection.path = '/torrent/' + urllib2.quote(url, '') + '/stream.mp4' connection.splittedpath = connection.path.split('/') From c7b945c2c4c702c652e7bf0aa464fc0c8d843752 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 18 Mar 2016 21:28:24 +0300 Subject: [PATCH 30/95] Set the Content-Length header. --- plugins/p2pproxy_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 027f738..0a72728 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -129,6 +129,7 @@ def handle(self, connection, headers_only=False): exported = exported.encode('utf-8') connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') + connection.send_header('Content-Length', str(len(exported))) connection.end_headers() connection.wfile.write(exported) else: # /channels/?filter=[filter] From c22da118710bbfd9c7c9173f416b4f93f13c0397 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 18 Mar 2016 22:08:50 +0300 Subject: [PATCH 31/95] Update logos if the p2p plugin is configured --- plugins/torrenttv_plugin.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 2c41ed7..ac1feb4 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -14,6 +14,8 @@ from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator import config.torrenttv as config +import config.p2pproxy as p2pconfig +from torrenttv_api import TorrentTvApi class Torrenttv(AceProxyPlugin): @@ -28,6 +30,8 @@ def __init__(self, AceConfig, AceStuff): self.playlist = None self.playlisttime = None self.etag = None + self.logomap = config.logomap + self.updatelogos = p2pconfig.email != 're.place@me' and p2pconfig.password != 'ReplaceMe' if config.updateevery: gevent.spawn(self.playlistTimedDownloader) @@ -71,6 +75,23 @@ def downloadPlaylist(self): self.logger.error("Can't download playlist!") self.logger.error(traceback.format_exc()) return False + + if self.updatelogos: + try: + api = TorrentTvApi(p2pconfig.email, p2pconfig.password, p2pconfig.sessiontimeout) + translations = api.translations('all') + logos = dict() + + for channel in translations: + name = channel.getAttribute('name').encode('utf-8') + logo = channel.getAttribute('logo').encode('utf-8') + logos[name] = config.logobase + logo + + self.logomap = logos + self.logger.debug("Logos updated") + except: + # p2pproxy plugin seems not configured + self.updatelogos = False return True From f3f7e2c4a2c7fc0d93d0bf26ee182b730954a8f6 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 19 Mar 2016 16:01:12 +0300 Subject: [PATCH 32/95] Fixed videodestroydelay --- aceclient/clientcounter.py | 5 +++++ acehttp.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index 532fd8a..f255db0 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -17,6 +17,11 @@ def __init__(self): self.total = 0 gevent.spawn(self.checkIdle) + def count(self, cid): + with self.lock: + clients = self.clients.get(cid) + return len(clients) if clients else 0 + def getClients(self, cid): with self.lock: return self.clients.get(cid) diff --git a/acehttp.py b/acehttp.py index 053730b..8ab6ab3 100755 --- a/acehttp.py +++ b/acehttp.py @@ -374,6 +374,11 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None): self.errorhappened = True self.dieWithError() finally: + if AceConfig.videodestroydelay and not self.errorhappened and AceStuff.clientcounter.count(cid) == 1: + # If no error happened and we are the only client + logger.debug("Sleeping for " + str(AceConfig.videodestroydelay) + " seconds") + gevent.sleep(AceConfig.videodestroydelay) + try: remaining = AceStuff.clientcounter.delete(cid, self.client) self.client.destroy() From 83d166d53fbac2198aafd4159a4ad6425ebfecf2 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 19 Mar 2016 22:29:45 +0300 Subject: [PATCH 33/95] Set the Content-Type header before writing data --- acehttp.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/acehttp.py b/acehttp.py index 8ab6ab3..f74f21f 100755 --- a/acehttp.py +++ b/acehttp.py @@ -355,6 +355,10 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None): # Run proxyReadWrite self.proxyReadWrite() else: + if not self.headerssent: + self.send_response(200) + self.send_header("Content-Type", "video/mpeg") + self.end_headers() self.client.handle(shouldStart, self.url) From d2f66376ccda64d04dd74b3d08a2187e8010425f Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 20 Mar 2016 15:31:07 +0300 Subject: [PATCH 34/95] Check the ace engine and vlc processes before handling a request --- aceclient/clientcounter.py | 8 ++++ acehttp.py | 84 +++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 38 deletions(-) diff --git a/aceclient/clientcounter.py b/aceclient/clientcounter.py index f255db0..bb5af56 100644 --- a/aceclient/clientcounter.py +++ b/aceclient/clientcounter.py @@ -111,6 +111,14 @@ def deleteAll(self, cid): for c in clients: c.destroy() + def destroyIdle(self): + with self.lock: + try: + if self.idleace: + self.idleace.destroy() + finally: + self.idleace = None + def createAce(self): logger = logging.getLogger('createAce') ace = aceclient.AceClient( diff --git a/acehttp.py b/acehttp.py index f74f21f..88980ba 100755 --- a/acehttp.py +++ b/acehttp.py @@ -426,9 +426,14 @@ def getCid(self, reqtype, url): class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + + def process_request(self, request, client_address): + checkVlc() + checkAce() + SocketServer.ThreadingMixIn.process_request(self, request, client_address) def handle_error(self, request, client_address): - # Do not print HTTP tracebacks + # logging.debug(traceback.format_exc()) pass @@ -437,8 +442,8 @@ class Client: def __init__(self, cid, handler, channelName, channelIcon): self.cid = cid self.handler = handler - self.channelName=channelName - self.channelIcon=channelIcon + self.channelName = channelName + self.channelIcon = channelIcon self.ace = None self.lock = threading.Condition(threading.Lock()) self.queue = deque() @@ -472,18 +477,19 @@ def handle(self, shouldStart, url): self.handler.end_headers() while self.handler.connected and self.ace._streamReaderState == 2: - data = self.getChunk(60.0) - - if data and self.handler.connected: - try: - self.handler.wfile.write(data) - except: + try: + data = self.getChunk(60.0) + + if data and self.handler.connected: + try: + self.handler.wfile.write(data) + except: + break + else: break - elif self.handler.connected: + except Queue.Empty: logger.debug("No data received in 60 seconds - disconnecting") - break - else: - break + def addChunk(self, chunk, timeout): start = time.time() @@ -527,8 +533,6 @@ class AceStuff(object): ''' Inter-class interaction class ''' - cidcache = dict() - # taken from http://stackoverflow.com/questions/2699907/dropping-root-permissions-in-python def drop_privileges(uid_name, gid_name='nogroup'): @@ -639,6 +643,16 @@ def connectVLC(): print repr(e) return False +def checkVlc(): + if AceConfig.vlcuse and AceConfig.vlcspawn and not isRunning(AceStuff.vlc): + del AceStuff.vlc + if spawnVLC(AceStuff.vlcProc, AceConfig.vlcspawntimeout) and connectVLC(): + logger.info("VLC died, respawned it with pid " + str(AceStuff.vlc.pid)) + else: + logger.error("Cannot spawn VLC!") + clean_proc() + sys.exit(1) + def spawnAce(cmd, delay=0): if AceConfig.osplatform == 'Windows': reg = _winreg.ConnectRegistry(None, _winreg.HKEY_CURRENT_USER) @@ -657,6 +671,22 @@ def spawnAce(cmd, delay=0): except: return False +def checkAce(): + if AceConfig.acespawn and not hasattr(AceStuff, 'ace') or not isRunning(AceStuff.ace): + AceStuff.clientcounter.destroyIdle() + if hasattr(AceStuff, 'ace'): + del AceStuff.ace + if spawnAce(AceStuff.aceProc, 1): + logger.info("Ace Stream died, respawned it with pid " + str(AceStuff.ace.pid)) + if AceConfig.osplatform == 'Windows': + # Wait some time because ace engine refreshes the acestream.port file only after full loading... + gevent.sleep(AceConfig.acestartuptimeout) + detectPort() + else: + logger.error("Cannot spawn Ace Stream!") + clean_proc() + sys.exit(1) + def detectPort(): try: if not isRunning(AceStuff.ace): @@ -809,7 +839,7 @@ def _reloadconfig(signum=None, frame=None): # Wait some time because ace engine refreshes the acestream.port file only after full loading... gevent.sleep(AceConfig.acestartuptimeout) detectPort() - + try: logger.info("Using gevent %s" % gevent.__version__) logger.info("Using psutil %s" % psutil.__version__) @@ -817,28 +847,6 @@ def _reloadconfig(signum=None, frame=None): logger.info("Using VLC %s" % AceStuff.vlcclient._vlcver) logger.info("Server started.") while True: - if AceConfig.vlcuse and AceConfig.vlcspawn: - if not isRunning(AceStuff.vlc): - del AceStuff.vlc - if spawnVLC(AceStuff.vlcProc, AceConfig.vlcspawntimeout) and connectVLC(): - logger.info("VLC died, respawned it with pid " + str(AceStuff.vlc.pid)) - else: - logger.error("Cannot spawn VLC!") - clean_proc() - sys.exit(1) - if AceConfig.acespawn and not isRunning(AceStuff.ace): - del AceStuff.ace - if spawnAce(AceStuff.aceProc, 1): - logger.info("Ace Stream died, respawned it with pid " + str(AceStuff.ace.pid)) - if AceConfig.osplatform == 'Windows': - # Wait some time because ace engine refreshes the acestream.port file only after full loading... - gevent.sleep(AceConfig.acestartuptimeout) - detectPort() - else: - logger.error("Cannot spawn Ace Stream!") - clean_proc() - sys.exit(1) - # Return to our server tasks server.handle_request() except (KeyboardInterrupt, SystemExit): shutdown() From 97a908bc867155a5e2d99a8b39d5cc22d10d6274 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 20 Mar 2016 16:01:11 +0300 Subject: [PATCH 35/95] Removed header duplicates --- acehttp.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/acehttp.py b/acehttp.py index 88980ba..6f49c76 100755 --- a/acehttp.py +++ b/acehttp.py @@ -355,10 +355,6 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None): # Run proxyReadWrite self.proxyReadWrite() else: - if not self.headerssent: - self.send_response(200) - self.send_header("Content-Type", "video/mpeg") - self.end_headers() self.client.handle(shouldStart, self.url) @@ -474,6 +470,7 @@ def handle(self, shouldStart, url): if self.handler.connected: self.handler.send_response(200) + self.handler.send_header("Content-Type", "video/mpeg") self.handler.end_headers() while self.handler.connected and self.ace._streamReaderState == 2: From 63533ec71adb54827c259bb1bb02e38b0935f3cb Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 22 Mar 2016 02:36:20 +0300 Subject: [PATCH 36/95] Fixes --- acehttp.py | 4 ---- plugins/p2pproxy_plugin.py | 4 ++-- plugins/torrenttv_plugin.py | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/acehttp.py b/acehttp.py index 6f49c76..823bfd2 100755 --- a/acehttp.py +++ b/acehttp.py @@ -356,11 +356,7 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None): self.proxyReadWrite() else: self.client.handle(shouldStart, self.url) - - # Waiting until hangDetector is joined - self.hanggreenlet.join() - logger.debug("Request handler finished") except (aceclient.AceException, vlcclient.VlcException, urllib2.URLError) as e: logger.error("Exception: " + repr(e)) self.errorhappened = True diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 0a72728..8d95a04 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -221,7 +221,7 @@ def handle(self, connection, headers_only=False): else: try: param_date = param_date.split('-') - d = date(param_date[2], param_date[1], param_date[0]) + d = date(int(param_date[2]), int(param_date[1]), int(param_date[0])) except IndexError: P2pproxy.logger.error('date param is not correct!') connection.dieWithError() @@ -296,7 +296,7 @@ def handle(self, connection, headers_only=False): else: try: param_date = param_date.split('-') - d = date(param_date[2], param_date[1], param_date[0]) + d = date(int(param_date[2]), int(param_date[1]), int(param_date[0])) except IndexError: P2pproxy.logger.error('date param is not correct!') connection.dieWithError() diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index ac1feb4..fdf67b4 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -109,6 +109,10 @@ def handle(self, connection, headers_only=False): url = urlparse.urlparse(connection.path) if url.path.startswith('/torrenttv/channel/'): + if not url.path.endswith('.mp4'): + connection.dieWithError() + return + name = urllib2.unquote(url.path[19:-4]).decode('UTF8') url = self.channels[name] connection.path = '/torrent/' + urllib2.quote(url, '') + '/stream.mp4' From 44f0afe341ce9a99f88ce5a7d5dcc6834672e149 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 22 Mar 2016 21:48:31 +0300 Subject: [PATCH 37/95] Added group names for the torrent-telik plugin --- plugins/torrenttelik_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/torrenttelik_plugin.py b/plugins/torrenttelik_plugin.py index 9989318..7c0af02 100644 --- a/plugins/torrenttelik_plugin.py +++ b/plugins/torrenttelik_plugin.py @@ -84,6 +84,7 @@ def handle(self, connection, headers_only=False): playlistgen = PlaylistGenerator() for channel in channels: + channel['group'] = channel.get('cat', '') playlistgen.addItem(channel) exported = playlistgen.exportm3u(hostport, add_ts=add_ts) From 06f4a00649a75bc3507550eb6d2c39bc496ab26a Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 23 Mar 2016 00:30:19 +0300 Subject: [PATCH 38/95] Implemented transcoding --- aceconfig.py | 20 +++++++++ acehttp.py | 64 +++++++++++++++++++--------- plugins/allfon_plugin.py | 6 ++- plugins/modules/PlaylistGenerator.py | 8 +++- plugins/p2pproxy_plugin.py | 8 ++-- plugins/torrenttelik_plugin.py | 2 +- plugins/torrenttv_plugin.py | 8 ++-- 7 files changed, 87 insertions(+), 29 deletions(-) diff --git a/aceconfig.py b/aceconfig.py index f4a63bd..0b907d0 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -125,6 +125,26 @@ class AceConfig(acedefconfig.AceDefConfig): # !!! # PLEASE set this to 0 if you use VLC # !!! + # + # ---------------------------------------------------- + # Transcoding configuration + # ---------------------------------------------------- + # Enable/disable transcoding + transcode = False + # Dictionary with a set of transcoding commands. Transcoding command is an + # executable commandline expression that reads an input stream from STDIN + # and writes a transcoded stream to STDOUT. The commands are selected + # according to the value of the 'fmt' request parameter. For example, the + # following url: + # http://loclahost:8000/channels/?type=m3u&fmt=mp2 + # contains the fmt=mp2. It means that the 'mp2' command will be used for + # transcoding. You may add any number of commands to this dictionary. + transcodecmd = dict() + # transcodecmd['mp2'] = 'ffmpeg -i - -c:a mp2 -c:v mpeg2video -f mpegts -qscale:v 2 -'.split() + # transcodecmd['mkv'] = 'ffmpeg -i - -c:a copy -c:v copy -f matroska -'.split() + # transcodecmd['default'] = 'ffmpeg -i - -c:a copy -c:v copy -f mpegts -'.split() + + # ---------------------------------------------------- videodelay = 0 # Obey PAUSE and RESUME commands from Engine # (stops sending data to client, should prevent annoying buffering) diff --git a/acehttp.py b/acehttp.py index 823bfd2..1bf54d3 100755 --- a/acehttp.py +++ b/acehttp.py @@ -18,6 +18,7 @@ import sys import logging import psutil +from subprocess import PIPE import BaseHTTPServer import SocketServer from socket import error as SocketException @@ -28,6 +29,7 @@ import time import threading import urllib2 +import urlparse import Queue import aceclient import aceconfig @@ -213,8 +215,11 @@ def do_GET(self, headers_only=False): return self.handleRequest(headers_only) - def handleRequest(self, headers_only, channelName=None, channelIcon=None): + def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=None): logger = logging.getLogger('handleRequest') + self.requrl = urlparse.urlparse(self.path) + self.reqparams = urlparse.parse_qs(self.requrl.query) + self.path = self.requrl.path[:-1] if self.requrl.path.endswith('/') else self.requrl.path # Check if third parameter exists # …/pid/blablablablabla/video.mpg @@ -355,7 +360,9 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None): # Run proxyReadWrite self.proxyReadWrite() else: - self.client.handle(shouldStart, self.url) + if not fmt: + fmt = self.reqparams.get('fmt')[0] if self.reqparams.has_key('fmt') else None + self.client.handle(shouldStart, self.url, fmt) except (aceclient.AceException, vlcclient.VlcException, urllib2.URLError) as e: logger.error("Exception: " + repr(e)) @@ -440,7 +447,7 @@ def __init__(self, cid, handler, channelName, channelIcon): self.lock = threading.Condition(threading.Lock()) self.queue = deque() - def handle(self, shouldStart, url): + def handle(self, shouldStart, url, fmt=None): logger = logging.getLogger("ClientHandler") if shouldStart: @@ -468,21 +475,39 @@ def handle(self, shouldStart, url): self.handler.send_response(200) self.handler.send_header("Content-Type", "video/mpeg") self.handler.end_headers() - - while self.handler.connected and self.ace._streamReaderState == 2: - try: - data = self.getChunk(60.0) - - if data and self.handler.connected: - try: - self.handler.wfile.write(data) - except: + + if AceConfig.transcode: + if not fmt or not AceConfig.transcodecmd.has_key(fmt): + fmt = 'default' + if AceConfig.transcodecmd.has_key(fmt): + stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL + transcoder = psutil.Popen(AceConfig.transcodecmd[fmt], bufsize=AceConfig.readchunksize, + stdin=PIPE, stdout=self.handler.wfile, stderr=stderr) + out = transcoder.stdin + else: + transcoder = None + out = self.handler.wfile + else: + transcoder = None + out = self.handler.wfile + + try: + while self.handler.connected and self.ace._streamReaderState == 2: + try: + data = self.getChunk(60.0) + + if data and self.handler.connected: + try: + out.write(data) + except: + break + else: break - else: - break - except Queue.Empty: - logger.debug("No data received in 60 seconds - disconnecting") - + except Queue.Empty: + logger.debug("No data received in 60 seconds - disconnecting") + finally: + if transcoder: + transcoder.kill() def addChunk(self, chunk, timeout): start = time.time() @@ -602,7 +627,7 @@ def drop_privileges(uid_name, gid_name='nogroup'): # Creating ClientCounter AceStuff.clientcounter = ClientCounter() -if AceConfig.vlcspawn or AceConfig.acespawn: +if AceConfig.vlcspawn or AceConfig.acespawn or AceConfig.transcode: DEVNULL = open(os.devnull, 'wb') # Spawning procedures @@ -665,7 +690,7 @@ def spawnAce(cmd, delay=0): return False def checkAce(): - if AceConfig.acespawn and not hasattr(AceStuff, 'ace') or not isRunning(AceStuff.ace): + if AceConfig.acespawn and not isRunning(AceStuff.ace): AceStuff.clientcounter.destroyIdle() if hasattr(AceStuff, 'ace'): del AceStuff.ace @@ -813,6 +838,7 @@ def _reloadconfig(signum=None, frame=None): else: name = 'acestreamengine' ace_pid = findProcess(name) +AceStuff.ace = None if not ace_pid: if AceConfig.acespawn: if AceConfig.osplatform == 'Windows': diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py index 61bec3a..1b2fec4 100644 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -5,6 +5,7 @@ import re import logging import urllib2 +import urlparse import time from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator @@ -70,4 +71,7 @@ def handle(self, connection, headers_only=False): for match in matches: playlistgen.addItem(match.groupdict()) - connection.wfile.write(playlistgen.exportm3u(hostport, add_ts=add_ts)) + url = urlparse.urlparse(connection.path) + params = urlparse.parse_qs(url.query) + fmt = params['fmt'][0] if params.has_key('fmt') else None + connection.wfile.write(playlistgen.exportm3u(hostport, add_ts=add_ts, fmt=fmt)) diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py index 4e5041a..a60b82d 100644 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -41,7 +41,7 @@ def _generatem3uline(item): return config.m3uchanneltemplate % item - def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, header=None): + def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, header=None, fmt=None): ''' Exports m3u playlist ''' @@ -80,6 +80,12 @@ def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive if url == item['url']: # For channel names item['url'] = re.sub('^([^/]+)$', lambda match: 'http://' + hostport + path + '/' + match.group(0), url, flags=re.MULTILINE) + + if fmt: + if '?' in item['url']: + item['url'] = item['url'] + '&fmt=' + fmt + else: + item['url'] = item['url'] + '/?fmt=' + fmt itemlist += PlaylistGenerator._generatem3uline(item) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 8d95a04..c1e4498 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -89,7 +89,7 @@ def handle(self, connection, headers_only=False): connection.path = stream_url connection.splittedpath = stream_url.split('/') connection.reqtype = connection.splittedpath[1].lower() - connection.handleRequest(headers_only) + connection.handleRequest(headers_only, fmt=self.get_param('fmt')) elif self.get_param('type') == 'm3u': # /channels/?filter=[filter]&group=[group]&type=m3u if headers_only: connection.send_response(200) @@ -125,7 +125,7 @@ def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Exporting') header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) - exported = playlistgen.exportm3u(hostport=hostport, header=header) + exported = playlistgen.exportm3u(hostport=hostport, header=header, fmt=self.get_param('fmt')) exported = exported.encode('utf-8') connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') @@ -213,7 +213,7 @@ def handle(self, connection, headers_only=False): connection.path = stream_url connection.splittedpath = stream_url.split('/') connection.reqtype = connection.splittedpath[1].lower() - connection.handleRequest(headers_only) + connection.handleRequest(headers_only, fmt=self.get_param('fmt')) elif self.get_param('type') == 'm3u': # /archive/?type=m3u&date=[param_date]&channel_id=[param_channel] param_date = self.get_param('date') if not param_date: @@ -281,7 +281,7 @@ def handle(self, connection, headers_only=False): playlistgen.addItem({'name': name, 'url': record_id, 'logo': logo}) P2pproxy.logger.debug('Exporting') - exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True) + exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True, fmt=self.get_param('fmt')) exported = exported.encode('utf-8') connection.send_response(200) diff --git a/plugins/torrenttelik_plugin.py b/plugins/torrenttelik_plugin.py index 7c0af02..b86d28d 100644 --- a/plugins/torrenttelik_plugin.py +++ b/plugins/torrenttelik_plugin.py @@ -87,7 +87,7 @@ def handle(self, connection, headers_only=False): channel['group'] = channel.get('cat', '') playlistgen.addItem(channel) - exported = playlistgen.exportm3u(hostport, add_ts=add_ts) + exported = playlistgen.exportm3u(hostport, add_ts=add_ts, fmt=self.getparam('fmt')) exported = exported.encode('utf-8') connection.wfile.write(exported) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index fdf67b4..a728cd5 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -66,7 +66,7 @@ def downloadPlaylist(self): if url.startswith('http://') and url.endswith('.acelive'): self.channels[name] = url - itemdict['url'] = urllib2.quote(encname, '') +'.mp4' + itemdict['url'] = urllib2.quote(encname, '') + '.mp4' m.update(encname) @@ -107,6 +107,8 @@ def handle(self, connection, headers_only=False): return url = urlparse.urlparse(connection.path) + params = urlparse.parse_qs(url.query) + fmt = params['fmt'][0] if params.has_key('fmt') else None if url.path.startswith('/torrenttv/channel/'): if not url.path.endswith('.mp4'): @@ -129,8 +131,8 @@ def handle(self, connection, headers_only=False): hostport = connection.headers['Host'] path = '' if len(self.channels) == 0 else '/torrenttv/channel' add_ts = True if url.path.endswith('/ts') else False - header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) - exported = self.playlist.exportm3u(hostport, path, add_ts=add_ts, header=header) + header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' % (config.tvgurl, config.tvgshift) + exported = self.playlist.exportm3u(hostport, path, add_ts=add_ts, header=header, fmt=fmt) connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') From 853238ebc4d88b62810ceec87bf6463ae7bcea5d Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 4 Apr 2016 23:47:48 +0300 Subject: [PATCH 39/95] Fixes --- acehttp.py | 7 +++++-- plugins/p2pproxy_plugin.py | 15 +++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/acehttp.py b/acehttp.py index 1bf54d3..dfd1333 100755 --- a/acehttp.py +++ b/acehttp.py @@ -379,8 +379,11 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No finally: if AceConfig.videodestroydelay and not self.errorhappened and AceStuff.clientcounter.count(cid) == 1: # If no error happened and we are the only client - logger.debug("Sleeping for " + str(AceConfig.videodestroydelay) + " seconds") - gevent.sleep(AceConfig.videodestroydelay) + try: + logger.debug("Sleeping for " + str(AceConfig.videodestroydelay) + " seconds") + gevent.sleep(AceConfig.videodestroydelay) + except: + pass try: remaining = AceStuff.clientcounter.delete(cid, self.client) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index c1e4498..53ac913 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -160,14 +160,15 @@ def handle(self, connection, headers_only=False): connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') - connection.send_header('Content-Length', str(len(translations_list))) connection.send_header('Content-Type', 'text/xml;charset=utf-8') - connection.end_headers() - if not headers_only: + if headers_only: + connection.end_headers() return translations_list = self.api.translations('all', True) + connection.send_header('Content-Length', str(len(translations_list))) + connection.end_headers() P2pproxy.logger.debug('Exporting') connection.wfile.write(translations_list) elif connection.reqtype == 'archive': # /archive/ branch @@ -185,6 +186,8 @@ def handle(self, connection, headers_only=False): connection.send_header('Content-Length', str(len(archive_channels))) connection.end_headers() connection.wfile.write(archive_channels) + + return if len(connection.splittedpath) == 3 and connection.splittedpath[2].split('?')[ 0] == 'play': # /archive/play?id=[record_id] record_id = self.get_param('id') @@ -265,11 +268,11 @@ def handle(self, connection, headers_only=False): for record in records_list: record_id = record.getAttribute('record_id') name = record.getAttribute('name') - channel_id = record.getAttribute('channel_id') + channel_id = record.getAttribute('epg_id') channel_name = '' logo = '' for channel in channels_list: - if channel.getAttribute('channel_id') == channel_id: + if channel.getAttribute('epg_id') == channel_id: channel_name = channel.getAttribute('name') logo = channel.getAttribute('logo') @@ -278,7 +281,7 @@ def handle(self, connection, headers_only=False): if logo != '' and config.fullpathlogo: logo = P2pproxy.TTVU + logo - playlistgen.addItem({'name': name, 'url': record_id, 'logo': logo}) + playlistgen.addItem({'name': name, 'url': record_id, 'logo': logo, 'tvg': ''}) P2pproxy.logger.debug('Exporting') exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True, fmt=self.get_param('fmt')) From dc947c020778ee1ec67af6df537b840f8865c975 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 5 Apr 2016 01:33:09 +0300 Subject: [PATCH 40/95] Implemented new handler for archives - /archive/playlist --- plugins/modules/PlaylistGenerator.py | 31 +++++----- plugins/p2pproxy_plugin.py | 93 +++++++++++++++++++++------- 2 files changed, 87 insertions(+), 37 deletions(-) diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py index a60b82d..d5019ea 100644 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -41,7 +41,7 @@ def _generatem3uline(item): return config.m3uchanneltemplate % item - def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, header=None, fmt=None): + def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, process_url=True, header=None, fmt=None): ''' Exports m3u playlist ''' @@ -64,22 +64,23 @@ def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive item['tvg'] = item.get('tvg', '') if item.has_key('tvg') else item.get('name').replace(' ', '_') url = item['url']; - # For .acelive and .torrent - item['url'] = re.sub('^(http.+)$', lambda match: 'http://' + hostport + path + '/torrent/' + \ - urllib2.quote(match.group(0), '') + '/stream.mp4', url, - flags=re.MULTILINE) - if url == item['url']: # For PIDs - item['url'] = re.sub('^(acestream://)?(?P[0-9a-f]{40})$', 'http://' + hostport + path + '/pid/\\g/stream.mp4', - url, flags=re.MULTILINE) - if archive and url == item['url']: # For archive channel id's - item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/archive/play?id=' + match.group(0), - url, flags=re.MULTILINE) - if not archive and url == item['url']: # For channel id's - item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/channels/play?id=' + match.group(0), + if process_url: + # For .acelive and .torrent + item['url'] = re.sub('^(http.+)$', lambda match: 'http://' + hostport + path + '/torrent/' + \ + urllib2.quote(match.group(0), '') + '/stream.mp4', url, + flags=re.MULTILINE) + if url == item['url']: # For PIDs + item['url'] = re.sub('^(acestream://)?(?P[0-9a-f]{40})$', 'http://' + hostport + path + '/pid/\\g/stream.mp4', url, flags=re.MULTILINE) - if url == item['url']: # For channel names - item['url'] = re.sub('^([^/]+)$', lambda match: 'http://' + hostport + path + '/' + match.group(0), + if archive and url == item['url']: # For archive channel id's + item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/archive/play?id=' + match.group(0), url, flags=re.MULTILINE) + if not archive and url == item['url']: # For channel id's + item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/channels/play?id=' + match.group(0), + url, flags=re.MULTILINE) + if url == item['url']: # For channel names + item['url'] = re.sub('^([^/]+)$', lambda match: 'http://' + hostport + path + '/' + match.group(0), + url, flags=re.MULTILINE) if fmt: if '?' in item['url']: diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 53ac913..9ba1fc6 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -22,7 +22,9 @@ import urllib2 import urlparse from torrenttv_api import TorrentTvApi -from datetime import date +from datetime import date, timedelta +import time + from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator @@ -120,11 +122,11 @@ def handle(self, connection, headers_only=False): logo = P2pproxy.TTVU + logo fields = {'name': name, 'id': cid, 'url': cid, 'group': group, 'logo': logo} - fields['tvgid'] = config.tvgid %fields + fields['tvgid'] = config.tvgid % fields playlistgen.addItem(fields) P2pproxy.logger.debug('Exporting') - header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) + header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' % (config.tvgurl, config.tvgshift) exported = playlistgen.exportm3u(hostport=hostport, header=header, fmt=self.get_param('fmt')) exported = exported.encode('utf-8') connection.send_response(200) @@ -172,7 +174,47 @@ def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Exporting') connection.wfile.write(translations_list) elif connection.reqtype == 'archive': # /archive/ branch - if len(connection.splittedpath) == 3 and connection.splittedpath[2] == 'channels': # /archive/channels + if len(connection.splittedpath) >= 3 and (connection.splittedpath[2] == 'playlist' or connection.splittedpath[2] == 'playlist.m3u'): # /archive/playlist.m3u + dates = list() + + if self.params.has_key('date'): + for d in self.params['date']: + dates.append(self.parse_date(d).strftime('%d-%m-%Y').replace('-0', '-')) + else: + d = date.today() + delta = timedelta(days=1) + for i in range(7): + dates.append(d.strftime('%d-%m-%Y').replace('-0', '-')) + d = d - delta + + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + + if headers_only: + connection.end_headers() + return + + channels_list = self.api.archive_channels() + hostport = connection.headers['Host'] + playlistgen = PlaylistGenerator() + + for channel in channels_list: + epg_id = channel.getAttribute('epg_id') + name = channel.getAttribute('name') + logo = channel.getAttribute('logo') + if logo != '' and config.fullpathlogo: + logo = P2pproxy.TTVU + logo + for d in dates: + n = name + ' (' + d + ')' + url = 'http://%s/archive/?type=m3u&date=%s&channel_id=%s' % (hostport, d, epg_id) + playlistgen.addItem({'group': name, 'tvg': '', 'name': n, 'url': url, 'logo': logo}) + + exported = playlistgen.exportm3u(hostport, empty_header=True, process_url=False, fmt=self.get_param('fmt')).encode('utf-8') + connection.send_header('Content-Length', str(len(exported))) + connection.end_headers() + connection.wfile.write(exported) + return + elif len(connection.splittedpath) == 3 and connection.splittedpath[2] == 'channels': # /archive/channels connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') @@ -218,17 +260,7 @@ def handle(self, connection, headers_only=False): connection.reqtype = connection.splittedpath[1].lower() connection.handleRequest(headers_only, fmt=self.get_param('fmt')) elif self.get_param('type') == 'm3u': # /archive/?type=m3u&date=[param_date]&channel_id=[param_channel] - param_date = self.get_param('date') - if not param_date: - d = date.today() # consider default date as today if not given - else: - try: - param_date = param_date.split('-') - d = date(int(param_date[2]), int(param_date[1]), int(param_date[0])) - except IndexError: - P2pproxy.logger.error('date param is not correct!') - connection.dieWithError() - return + d = self.get_date_param() if headers_only: connection.send_response(200) @@ -255,7 +287,7 @@ def handle(self, connection, headers_only=False): for record in records_list: name = record.getAttribute('name') record_id = record.getAttribute('record_id') - playlistgen.addItem({'group': channel_name, 'tvg': '', + playlistgen.addItem({'group': channel_name, 'tvg': '', 'name': name, 'url': record_id, 'logo': logo}) except: P2pproxy.logger.debug('Failed to load archive for ' + channel_id) @@ -267,9 +299,10 @@ def handle(self, connection, headers_only=False): for record in records_list: record_id = record.getAttribute('record_id') - name = record.getAttribute('name') channel_id = record.getAttribute('epg_id') - channel_name = '' + name = record.getAttribute('name') + d = time.localtime(float(record.getAttribute('time'))) + n = '%.2d:%.2d %s' % (d.tm_hour, d.tm_min, name) logo = '' for channel in channels_list: if channel.getAttribute('epg_id') == channel_id: @@ -281,7 +314,7 @@ def handle(self, connection, headers_only=False): if logo != '' and config.fullpathlogo: logo = P2pproxy.TTVU + logo - playlistgen.addItem({'name': name, 'url': record_id, 'logo': logo, 'tvg': ''}) + playlistgen.addItem({'group': channel_name, 'name': n, 'url': record_id, 'logo': logo, 'tvg': ''}) P2pproxy.logger.debug('Exporting') exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True, fmt=self.get_param('fmt')) @@ -324,13 +357,13 @@ def handle(self, connection, headers_only=False): connection.send_header('Content-Length', str(len(records_list))) connection.end_headers() connection.wfile.write(records_list) - elif connection.reqtype == 'logos': # Used to generate logomap for the torrenttv plugin + elif connection.reqtype == 'logos': # Used to generate logomap for the torrenttv plugin translations_list = self.api.translations('all') last = translations_list[-1] connection.send_response(200) connection.send_header('Content-Type', 'text/plain;charset=utf-8') connection.end_headers() - connection.wfile.write("logobase = '" + P2pproxy.TTVU +"'\n") + connection.wfile.write("logobase = '" + P2pproxy.TTVU + "'\n") connection.wfile.write("logomap = {\n") for channel in translations_list: @@ -348,4 +381,20 @@ def get_param(self, key): if key in self.params: return self.params[key][0] else: - return None \ No newline at end of file + return None + + def get_date_param(self): + d = self.get_param('date') + + if not d: + return date.today() + else: + return self.parse_date(d) + + def parse_date(self, d): + try: + param_date = d.split('-') + return date(int(param_date[2]), int(param_date[1]), int(param_date[0])) + except IndexError as e: + P2pproxy.logger.error('date param is not correct!') + raise e From 71e91f1aca5217544c5db477a9f77280c505c6c0 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 5 Apr 2016 01:49:38 +0300 Subject: [PATCH 41/95] Handle /channels.m3u requests in the same way as /channels/?type=m3u --- plugins/p2pproxy_plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 9ba1fc6..470c5ab 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -34,7 +34,7 @@ class P2pproxy(AceProxyPlugin): TTV = 'http://torrent-tv.ru/' TTVU = TTV + 'uploads/' - handlers = ('channels', 'archive', 'xbmc.pvr', 'logos') + handlers = ('channels', 'channels.m3u', 'archive', 'xbmc.pvr', 'logos') logger = logging.getLogger('plugin_p2pproxy') def __init__(self, AceConfig, AceStuff): @@ -50,7 +50,7 @@ def handle(self, connection, headers_only=False): query = urlparse.urlparse(connection.path).query self.params = urlparse.parse_qs(query) - if connection.reqtype == 'channels': # /channels/ branch + if connection.reqtype == 'channels' or connection.reqtype == 'channels.m3u': # /channels/ branch if len(connection.splittedpath) == 3 and connection.splittedpath[2].split('?')[ 0] == 'play': # /channels/play?id=[id] channel_id = self.get_param('id') @@ -92,7 +92,7 @@ def handle(self, connection, headers_only=False): connection.splittedpath = stream_url.split('/') connection.reqtype = connection.splittedpath[1].lower() connection.handleRequest(headers_only, fmt=self.get_param('fmt')) - elif self.get_param('type') == 'm3u': # /channels/?filter=[filter]&group=[group]&type=m3u + elif connection.reqtype == 'channels.m3u' or self.get_param('type') == 'm3u': # /channels/?filter=[filter]&group=[group]&type=m3u if headers_only: connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') From cd6757695bb029a4fed7321352f1ae7b71ab1a17 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 5 Apr 2016 21:57:46 +0300 Subject: [PATCH 42/95] Added support for playlist url suffix. --- plugins/p2pproxy_plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 470c5ab..b0929d8 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -197,6 +197,7 @@ def handle(self, connection, headers_only=False): channels_list = self.api.archive_channels() hostport = connection.headers['Host'] playlistgen = PlaylistGenerator() + suffix = '&suffix=' + self.get_param('suffix') if self.params.has_key('suffix') else '' for channel in channels_list: epg_id = channel.getAttribute('epg_id') @@ -206,7 +207,7 @@ def handle(self, connection, headers_only=False): logo = P2pproxy.TTVU + logo for d in dates: n = name + ' (' + d + ')' - url = 'http://%s/archive/?type=m3u&date=%s&channel_id=%s' % (hostport, d, epg_id) + url = 'http://%s/archive/?type=m3u&date=%s&channel_id=%s%s' % (hostport, d, epg_id, suffix) playlistgen.addItem({'group': name, 'tvg': '', 'name': n, 'url': url, 'logo': logo}) exported = playlistgen.exportm3u(hostport, empty_header=True, process_url=False, fmt=self.get_param('fmt')).encode('utf-8') From b754d168ae3c54d8dc5b931d7b3e2f8fb64550b9 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 6 Apr 2016 23:40:48 +0300 Subject: [PATCH 43/95] New playlist - /archive/dates.m3u --- plugins/p2pproxy_plugin.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index b0929d8..5bf78dc 100644 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -174,7 +174,26 @@ def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Exporting') connection.wfile.write(translations_list) elif connection.reqtype == 'archive': # /archive/ branch - if len(connection.splittedpath) >= 3 and (connection.splittedpath[2] == 'playlist' or connection.splittedpath[2] == 'playlist.m3u'): # /archive/playlist.m3u + if len(connection.splittedpath) >= 3 and (connection.splittedpath[2] == 'dates' or connection.splittedpath[2] == 'dates.m3u'): # /archive/dates.m3u + d = date.today() + delta = timedelta(days=1) + playlistgen = PlaylistGenerator() + hostport = connection.headers['Host'] + days = int(self.get_param('days')) if self.params.has_key('days') else 7 + suffix = '&suffix=' + self.get_param('suffix') if self.params.has_key('suffix') else '' + for i in range(days): + dfmt = d.strftime('%d-%m-%Y') + url = 'http://%s/archive/playlist/?date=%s%s' % (hostport, dfmt, suffix) + playlistgen.addItem({'group': '', 'tvg': '', 'name': dfmt, 'url': url}) + d = d - delta + exported = playlistgen.exportm3u(hostport, empty_header=True, process_url=False, fmt=self.get_param('fmt')).encode('utf-8') + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.send_header('Content-Length', str(len(exported))) + connection.end_headers() + connection.wfile.write(exported) + return + elif len(connection.splittedpath) >= 3 and (connection.splittedpath[2] == 'playlist' or connection.splittedpath[2] == 'playlist.m3u'): # /archive/playlist.m3u dates = list() if self.params.has_key('date'): @@ -183,7 +202,8 @@ def handle(self, connection, headers_only=False): else: d = date.today() delta = timedelta(days=1) - for i in range(7): + days = int(self.get_param('days')) if self.params.has_key('days') else 7 + for i in range(days): dates.append(d.strftime('%d-%m-%Y').replace('-0', '-')) d = d - delta @@ -206,7 +226,7 @@ def handle(self, connection, headers_only=False): if logo != '' and config.fullpathlogo: logo = P2pproxy.TTVU + logo for d in dates: - n = name + ' (' + d + ')' + n = name + ' (' + d + ')' if len(dates) > 1 else name url = 'http://%s/archive/?type=m3u&date=%s&channel_id=%s%s' % (hostport, d, epg_id, suffix) playlistgen.addItem({'group': name, 'tvg': '', 'name': n, 'url': url, 'logo': logo}) From e378999465e0a00882f4ce1975867c3f0620a30c Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Tue, 12 Apr 2016 22:46:07 +0300 Subject: [PATCH 44/95] Add new API features support for aceengine >=3.1.5 --- aceclient/aceclient.py | 13 +++++++++++-- aceclient/acemessages.py | 7 +++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/aceclient/aceclient.py b/aceclient/aceclient.py index b75e8b4..122130c 100644 --- a/aceclient/aceclient.py +++ b/aceclient/aceclient.py @@ -69,6 +69,7 @@ def __init__(self, host, port, connect_timeout=5, result_timeout=10): self._streamReaderConnection = None self._streamReaderState = None self._streamReaderQueue = deque() + self._engine_version_code = 0; # Logger logger = logging.getLogger('AceClieimport tracebacknt_init') @@ -170,8 +171,9 @@ def START(self, datatype, value): ''' Start video method ''' + stream_type = 'output_format=http' if self._engine_version_code >= 3010500 and not AceConfig.vlcuse else '' self._urlresult = AsyncResult() - self._write(AceMessage.request.START(datatype.upper(), value)) + self._write(AceMessage.request.START(datatype.upper(), value, stream_type)) self._getResult() def STOP(self): @@ -214,7 +216,10 @@ def startStreamReader(self, url, cid, counter): try: connection = self._streamReaderConnection = urllib2.urlopen(url) - + + if url.endswith('.m3u8'): + logger.debug("Can't stream HLS in non VLC mode: %s" % url) + if connection.getcode() != 200: logger.error("Failed to open video stream %s" % connection) return @@ -311,6 +316,10 @@ def _recvData(self): # Parsing everything only if the string is not empty if self._recvbuffer.startswith(AceMessage.response.HELLO): # Parse HELLO + if 'version_code=' in self._recvbuffer: + v = self._recvbuffer.find('version_code=') + self._engine_version_code = int(self._recvbuffer[v+13:v+20]) + if 'key=' in self._recvbuffer: self._request_key_begin = self._recvbuffer.find('key=') self._request_key = \ diff --git a/aceclient/acemessages.py b/aceclient/acemessages.py index 86449d8..1b0827c 100644 --- a/aceclient/acemessages.py +++ b/aceclient/acemessages.py @@ -5,8 +5,7 @@ import hashlib import platform import urllib2 - - + class AceConst(object): APIVERSION = 3 @@ -78,7 +77,7 @@ def LOADASYNC(command, request_id, params_dict): # End LOADASYNC @staticmethod - def START(command, params_dict): + def START(command, params_dict, stream_type): if command == 'TORRENT': return 'START TORRENT ' + str(params_dict.get('url')) + ' ' + \ str(params_dict.get('file_indexes', '0')) + ' ' + \ @@ -96,7 +95,7 @@ def START(command, params_dict): elif command == 'PID': return 'START PID ' + str(params_dict.get('content_id')) + ' ' + \ - str(params_dict.get('file_indexes', '0')) + str(params_dict.get('file_indexes', '0')) + ' ' + stream_type elif command == 'RAW': return 'START RAW ' + str(params_dict.get('data')) + ' ' + \ From 2086a61da5ee7a79b1e95cd41d4cf7be7c7861fe Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Tue, 12 Apr 2016 22:50:07 +0300 Subject: [PATCH 45/95] Add support playlist.py for allfon & torrent-telik --- plugins/allfon_plugin.py | 4 +++- plugins/torrenttelik_plugin.py | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py index 1b2fec4..0700013 100644 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -74,4 +74,6 @@ def handle(self, connection, headers_only=False): url = urlparse.urlparse(connection.path) params = urlparse.parse_qs(url.query) fmt = params['fmt'][0] if params.has_key('fmt') else None - connection.wfile.write(playlistgen.exportm3u(hostport, add_ts=add_ts, fmt=fmt)) + header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) + connection.wfile.write(playlistgen.exportm3u(hostport, header=header, add_ts=add_ts, fmt=fmt)) + diff --git a/plugins/torrenttelik_plugin.py b/plugins/torrenttelik_plugin.py index b86d28d..ada75e9 100644 --- a/plugins/torrenttelik_plugin.py +++ b/plugins/torrenttelik_plugin.py @@ -87,10 +87,12 @@ def handle(self, connection, headers_only=False): channel['group'] = channel.get('cat', '') playlistgen.addItem(channel) - exported = playlistgen.exportm3u(hostport, add_ts=add_ts, fmt=self.getparam('fmt')) - exported = exported.encode('utf-8') + Torrenttelik.logger.debug('Exporting') + header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) + exported = playlistgen.exportm3u(hostport, header=header, add_ts=add_ts, fmt=self.getparam('fmt')) + exported = exported.encode('utf-8') connection.wfile.write(exported) - + def getparam(self, key): if key in self.params: return self.params[key][0] From 1f73bc2d54cb5256edb90d9566224b82de27422d Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Tue, 12 Apr 2016 22:51:17 +0300 Subject: [PATCH 46/95] add EPG & TIMESHIFT for allfon & torrent-telik --- plugins/config/allfon.py | 4 ++++ plugins/config/torrenttelik.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/config/allfon.py b/plugins/config/allfon.py index 75d336b..4e94fce 100644 --- a/plugins/config/allfon.py +++ b/plugins/config/allfon.py @@ -4,3 +4,7 @@ # Insert your allfon.tv playlist URL here url = 'http://allfon.tv/autogenplaylist/allfontv.m3u' + +# EPG urls & EPG timeshift +tvgurl = 'http://www.teleguide.info/download/new3/jtv.zip' +tvgshift = 0 diff --git a/plugins/config/torrenttelik.py b/plugins/config/torrenttelik.py index 3ce57df..5dce26e 100644 --- a/plugins/config/torrenttelik.py +++ b/plugins/config/torrenttelik.py @@ -6,4 +6,8 @@ # Channels urls url_ttv = 'http://torrent-telik.com/channels/torrent-tv.json' url_mob_ttv = 'http://torrent-telik.com/channels/mob-torrent-tv.json' -url_allfon = 'http://torrent-telik.com/channels/allfon.json' \ No newline at end of file +url_allfon = 'http://torrent-telik.com/channels/allfon.json' + +# EPG urls & EPG timeshift +tvgurl = 'http://simple-tv.torrent-telik.com/teleprograma' +tvgshift = 0 From d4041af07d04f7c6707084193efb9246898313bb Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 13 Apr 2016 00:01:02 +0300 Subject: [PATCH 47/95] Added missing return --- aceclient/aceclient.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aceclient/aceclient.py b/aceclient/aceclient.py index 122130c..69155c6 100644 --- a/aceclient/aceclient.py +++ b/aceclient/aceclient.py @@ -218,7 +218,8 @@ def startStreamReader(self, url, cid, counter): connection = self._streamReaderConnection = urllib2.urlopen(url) if url.endswith('.m3u8'): - logger.debug("Can't stream HLS in non VLC mode: %s" % url) + logger.debug("Can't stream HLS in non VLC mode: %s" % url) + return if connection.getcode() != 200: logger.error("Failed to open video stream %s" % connection) From 54d9543ace3259fc508f17ac3b6704c19e9dca69 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 13 Apr 2016 00:02:49 +0300 Subject: [PATCH 48/95] Added handler for favicon.ico --- plugins/stat_plugin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index 82a8543..60c63de 100644 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -7,13 +7,17 @@ class Stat(AceProxyPlugin): - handlers = ('stat',) + handlers = ('stat', 'favicon.ico') def __init__(self, AceConfig, AceStuff): self.config = AceConfig self.stuff = AceStuff def handle(self, connection, headers_only=False): + if connection.reqtype == 'favicon.ico': + connection.send_response(404) + return + connection.send_response(200) connection.send_header('Content-type', 'text/html; charset=utf-8') connection.end_headers() From d04c98dd6b546d92206817e0d4977269f83220c8 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 13 Apr 2016 00:04:11 +0300 Subject: [PATCH 49/95] Print VLC messages to log if DEBUG is enabled. --- acehttp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/acehttp.py b/acehttp.py index dfd1333..84bf207 100755 --- a/acehttp.py +++ b/acehttp.py @@ -648,7 +648,9 @@ def spawnVLC(cmd, delay=0): dir = _winreg.QueryValueEx(key, 'InstallDir') playerdir = os.path.dirname(dir[0] + '\\player\\') cmd[0] = playerdir + '\\' + cmd[0] - AceStuff.vlc = psutil.Popen(cmd, stdout=DEVNULL, stderr=DEVNULL) + stdout = None if AceConfig.loglevel == logging.DEBUG else DEVNULL + stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL + AceStuff.vlc = psutil.Popen(cmd, stdout=stdout, stderr=stderr) gevent.sleep(delay) return True except: From 3465cdc71e4696d8bdb8f150f8a6df0246ecd03a Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 23 Apr 2016 17:18:54 +0300 Subject: [PATCH 50/95] Do not ignore acestream:// urls. --- plugins/torrenttv_plugin.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index a728cd5..5fee671 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -63,8 +63,8 @@ def downloadPlaylist(self): if logo: itemdict['logo'] = logo - - if url.startswith('http://') and url.endswith('.acelive'): + + if (url.startswith('acestream://')) or (url.startswith('http://') and url.endswith('.acelive')): self.channels[name] = url itemdict['url'] = urllib2.quote(encname, '') + '.mp4' @@ -117,9 +117,14 @@ def handle(self, connection, headers_only=False): name = urllib2.unquote(url.path[19:-4]).decode('UTF8') url = self.channels[name] - connection.path = '/torrent/' + urllib2.quote(url, '') + '/stream.mp4' - connection.splittedpath = connection.path.split('/') - connection.reqtype = 'torrent' + if url.startswith('acestream://'): + connection.path = '/pid/' + url[12:] + '/stream.mp4' + connection.splittedpath = connection.path.split('/') + connection.reqtype = 'pid' + else: + connection.path = '/torrent/' + urllib2.quote(url, '') + '/stream.mp4' + connection.splittedpath = connection.path.split('/') + connection.reqtype = 'torrent' play = True elif self.etag == connection.headers.get('If-None-Match'): self.logger.debug('ETag matches - returning 304') @@ -129,7 +134,7 @@ def handle(self, connection, headers_only=False): return else: hostport = connection.headers['Host'] - path = '' if len(self.channels) == 0 else '/torrenttv/channel' + path = '' if len(self.channels) == 0 else '/torrenttv/channel' add_ts = True if url.path.endswith('/ts') else False header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' % (config.tvgurl, config.tvgshift) exported = self.playlist.exportm3u(hostport, path, add_ts=add_ts, header=header, fmt=fmt) From 8769e63b557b7ca0641ab7b009898d8831c3dc34 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 23 Apr 2016 18:26:32 +0300 Subject: [PATCH 51/95] Changed host to allfon.org. --- plugins/allfon_plugin.py | 2 +- plugins/config/allfon.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py index 0700013..f8659ab 100644 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -56,7 +56,7 @@ def handle(self, connection, headers_only=False): # Match playlist with regexp - matches = re.finditer(r'\#EXTINF\:0\,ALLFON\.TV (?P\S.+)\n.+\n.+\n(?P^acestream.+$)', + matches = re.finditer(r'\#EXTINF\:0\,ALLFON\.ORG (?P\S.+)\n.+\n.+\n(?P^acestream.+$)', Allfon.playlist, re.MULTILINE) add_ts = False diff --git a/plugins/config/allfon.py b/plugins/config/allfon.py index 4e94fce..ad22073 100644 --- a/plugins/config/allfon.py +++ b/plugins/config/allfon.py @@ -3,7 +3,7 @@ ''' # Insert your allfon.tv playlist URL here -url = 'http://allfon.tv/autogenplaylist/allfontv.m3u' +url = 'http://allfon.org/autogenplaylist/allfontv.m3u' # EPG urls & EPG timeshift tvgurl = 'http://www.teleguide.info/download/new3/jtv.zip' From 7725ff28b326778f8bd5b9bb9e219c9bea4b9524 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 24 Apr 2016 21:27:10 +0300 Subject: [PATCH 52/95] Spawn the hang detector just before the proxy --- acehttp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acehttp.py b/acehttp.py index 84bf207..e7ca9b9 100755 --- a/acehttp.py +++ b/acehttp.py @@ -281,10 +281,6 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No self.headerssent = True try: - self.hanggreenlet = gevent.spawn(self.hangDetector) - logger.debug("hangDetector spawned") - gevent.sleep() - # Initializing AceClient if shouldStart: if contentid: @@ -325,6 +321,10 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No # Sleep a bit, because sometimes VLC doesn't open port in # time gevent.sleep(0.5) + + self.hanggreenlet = gevent.spawn(self.hangDetector) + logger.debug("hangDetector spawned") + gevent.sleep() # Building new VLC url if AceConfig.vlcuse: From cb8c8f2c35cf596dea4dc81878aecfcf46b8e6d0 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 5 May 2016 02:12:41 +0300 Subject: [PATCH 53/95] Show channel logo and name in /stat. Minor enhancements. --- plugins/allfon_plugin.py | 16 +++---- plugins/config/allfon.py | 3 ++ plugins/config/playlist.py | 3 +- plugins/modules/PlaylistGenerator.py | 16 ++++--- plugins/p2pproxy_plugin.py | 67 +++++++++++++++------------- plugins/stat_plugin.py | 4 +- plugins/torrenttv_api.py | 25 ++++++----- 7 files changed, 74 insertions(+), 60 deletions(-) mode change 100644 => 100755 plugins/allfon_plugin.py mode change 100644 => 100755 plugins/config/playlist.py mode change 100644 => 100755 plugins/modules/PlaylistGenerator.py mode change 100644 => 100755 plugins/p2pproxy_plugin.py mode change 100644 => 100755 plugins/stat_plugin.py mode change 100644 => 100755 plugins/torrenttv_api.py diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py old mode 100644 new mode 100755 index f8659ab..eacedc0 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -26,11 +26,9 @@ def __init__(self, AceConfig, AceStuff): def downloadPlaylist(self): try: - Allfon.logger.debug('Trying to download playlist') - print(config.url) + Allfon.logger.debug('Trying to download playlist: ' + config.url) req = urllib2.Request(config.url, headers={'User-Agent' : "Magic Browser"}) - Allfon.playlist = urllib2.urlopen( - req, timeout=10).read() + Allfon.playlist = urllib2.urlopen(req, timeout=10).read() Allfon.playlisttime = int(time.time()) except: Allfon.logger.error("Can't download playlist!") @@ -50,7 +48,7 @@ def handle(self, connection, headers_only=False): connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') connection.end_headers() - + if headers_only: return; @@ -58,16 +56,16 @@ def handle(self, connection, headers_only=False): matches = re.finditer(r'\#EXTINF\:0\,ALLFON\.ORG (?P\S.+)\n.+\n.+\n(?P^acestream.+$)', Allfon.playlist, re.MULTILINE) - + add_ts = False try: if connection.splittedpath[2].lower() == 'ts': add_ts = True except: pass - - playlistgen = PlaylistGenerator() + + playlistgen = PlaylistGenerator(m3uchanneltemplate=config.m3uchanneltemplate) for match in matches: playlistgen.addItem(match.groupdict()) @@ -76,4 +74,4 @@ def handle(self, connection, headers_only=False): fmt = params['fmt'][0] if params.has_key('fmt') else None header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) connection.wfile.write(playlistgen.exportm3u(hostport, header=header, add_ts=add_ts, fmt=fmt)) - + diff --git a/plugins/config/allfon.py b/plugins/config/allfon.py index ad22073..d458e15 100644 --- a/plugins/config/allfon.py +++ b/plugins/config/allfon.py @@ -8,3 +8,6 @@ # EPG urls & EPG timeshift tvgurl = 'http://www.teleguide.info/download/new3/jtv.zip' tvgshift = 0 + +# Channel template +m3uchanneltemplate = '#EXTINF:-1 tvg-name="%(tvg)s",%(name)s\n%(url)s\n' \ No newline at end of file diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py old mode 100644 new mode 100755 index 481a945..5261ac9 --- a/plugins/config/playlist.py +++ b/plugins/config/playlist.py @@ -1,8 +1,7 @@ # Default playlist format m3uemptyheader = '#EXTM3U\n' -m3uheader = \ - '#EXTM3U url-tvg="http://1ttvapi.top/ttv.xmltv.xml.gz"\n' +m3uheader = '#EXTM3U url-tvg="http://1ttvapi.top/ttv.xmltv.xml.gz"\n' # If you need the #EXTGRP field put this #EXTGRP:%(group)s\n after %(name)s\n. m3uchanneltemplate = \ '#EXTINF:-1 group-title="%(group)s" tvg-name="%(tvg)s" tvg-id="%(tvgid)s" tvg-logo="%(logo)s",%(name)s\n%(url)s\n' diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py old mode 100644 new mode 100755 index d5019ea..7dd0446 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -9,8 +9,11 @@ class PlaylistGenerator(object): - def __init__(self): + def __init__(self, m3uemptyheader=config.m3uemptyheader, m3uheader=config.m3uheader, m3uchanneltemplate=config.m3uchanneltemplate): self.itemlist = list() + self.m3uemptyheader = m3uemptyheader + self.m3uheader = m3uheader + self.m3uchanneltemplate = m3uchanneltemplate def addItem(self, itemdict): ''' @@ -25,8 +28,7 @@ def addItem(self, itemdict): ''' self.itemlist.append(itemdict) - @staticmethod - def _generatem3uline(item): + def _generatem3uline(self, item): ''' Generates EXTINF line with url ''' @@ -39,7 +41,7 @@ def _generatem3uline(item): if not item.has_key('logo'): item['logo'] = '' - return config.m3uchanneltemplate % item + return self.m3uchanneltemplate % item def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, process_url=True, header=None, fmt=None): ''' @@ -52,9 +54,9 @@ def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive if header is None: if not empty_header: - itemlist = config.m3uheader + itemlist = self.m3uheader else: - itemlist = config.m3uemptyheader + itemlist = self.m3uemptyheader else: itemlist = header @@ -88,6 +90,6 @@ def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive else: item['url'] = item['url'] + '/?fmt=' + fmt - itemlist += PlaylistGenerator._generatem3uline(item) + itemlist += self._generatem3uline(item) return itemlist diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py old mode 100644 new mode 100755 index 5bf78dc..62ba435 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -25,14 +25,13 @@ from datetime import date, timedelta import time - from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator import config.p2pproxy as config class P2pproxy(AceProxyPlugin): - TTV = 'http://torrent-tv.ru/' + TTV = 'http://1ttv.org/' TTVU = TTV + 'uploads/' handlers = ('channels', 'channels.m3u', 'archive', 'xbmc.pvr', 'logos') logger = logging.getLogger('plugin_p2pproxy') @@ -63,13 +62,13 @@ def handle(self, connection, headers_only=False): connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/plain;charset=utf-8') - connection.send_header('Server', 'P2pProxy/1.0.3.1 AceProxy') + connection.send_header('Server', 'P2pProxy/1.0.4.4 AceProxy') connection.wfile.write('\r\n') return else: connection.dieWithError() # Bad request return - + if headers_only: connection.send_response(200) connection.send_header("Content-Type", "video/mpeg") @@ -77,8 +76,16 @@ def handle(self, connection, headers_only=False): return stream_url = None + stream_type, stream, translations_list = self.api.stream_source(channel_id) + name=logo='' - stream_type, stream = self.api.stream_source(channel_id) + for channel in translations_list: + if channel.getAttribute('id') == channel_id: + name = channel.getAttribute('name') + logo = channel.getAttribute('logo') + if config.fullpathlogo: + logo = P2pproxy.TTVU + logo + break if stream_type == 'torrent': stream_url = re.sub('^(http.+)$', @@ -91,7 +98,7 @@ def handle(self, connection, headers_only=False): connection.path = stream_url connection.splittedpath = stream_url.split('/') connection.reqtype = connection.splittedpath[1].lower() - connection.handleRequest(headers_only, fmt=self.get_param('fmt')) + connection.handleRequest(headers_only, name, logo, fmt=self.get_param('fmt')) elif connection.reqtype == 'channels.m3u' or self.get_param('type') == 'm3u': # /channels/?filter=[filter]&group=[group]&type=m3u if headers_only: connection.send_response(200) @@ -142,7 +149,7 @@ def handle(self, connection, headers_only=False): connection.send_header('Content-Type', 'text/xml;charset=utf-8') connection.end_headers() return - + param_filter = self.get_param('filter') if not param_filter: param_filter = 'all' # default filter @@ -163,11 +170,11 @@ def handle(self, connection, headers_only=False): connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') - + if headers_only: connection.end_headers() return - + translations_list = self.api.translations('all', True) connection.send_header('Content-Length', str(len(translations_list))) connection.end_headers() @@ -195,7 +202,7 @@ def handle(self, connection, headers_only=False): return elif len(connection.splittedpath) >= 3 and (connection.splittedpath[2] == 'playlist' or connection.splittedpath[2] == 'playlist.m3u'): # /archive/playlist.m3u dates = list() - + if self.params.has_key('date'): for d in self.params['date']: dates.append(self.parse_date(d).strftime('%d-%m-%Y').replace('-0', '-')) @@ -206,19 +213,19 @@ def handle(self, connection, headers_only=False): for i in range(days): dates.append(d.strftime('%d-%m-%Y').replace('-0', '-')) d = d - delta - + connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') - + if headers_only: connection.end_headers() return - + channels_list = self.api.archive_channels() hostport = connection.headers['Host'] playlistgen = PlaylistGenerator() suffix = '&suffix=' + self.get_param('suffix') if self.params.has_key('suffix') else '' - + for channel in channels_list: epg_id = channel.getAttribute('epg_id') name = channel.getAttribute('name') @@ -240,7 +247,7 @@ def handle(self, connection, headers_only=False): connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') - + if headers_only: connection.end_headers() else: @@ -249,7 +256,7 @@ def handle(self, connection, headers_only=False): connection.send_header('Content-Length', str(len(archive_channels))) connection.end_headers() connection.wfile.write(archive_channels) - + return if len(connection.splittedpath) == 3 and connection.splittedpath[2].split('?')[ 0] == 'play': # /archive/play?id=[record_id] @@ -257,7 +264,7 @@ def handle(self, connection, headers_only=False): if not record_id: connection.dieWithError() # Bad request return - + if headers_only: connection.send_response(200) connection.send_header("Content-Type", "video/mpeg") @@ -282,7 +289,7 @@ def handle(self, connection, headers_only=False): connection.handleRequest(headers_only, fmt=self.get_param('fmt')) elif self.get_param('type') == 'm3u': # /archive/?type=m3u&date=[param_date]&channel_id=[param_channel] d = self.get_date_param() - + if headers_only: connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') @@ -292,10 +299,10 @@ def handle(self, connection, headers_only=False): playlistgen = PlaylistGenerator() param_channel = self.get_param('channel_id') d = d.strftime('%d-%m-%Y').replace('-0', '-') - + if param_channel == '' or not param_channel: channels_list = self.api.archive_channels() - + for channel in channels_list: channel_id = channel.getAttribute('epg_id') try: @@ -304,7 +311,7 @@ def handle(self, connection, headers_only=False): logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = P2pproxy.TTVU + logo - + for record in records_list: name = record.getAttribute('name') record_id = record.getAttribute('record_id') @@ -317,7 +324,7 @@ def handle(self, connection, headers_only=False): records_list = self.api.records(param_channel, d) channels_list = self.api.archive_channels() P2pproxy.logger.debug('Generating archive m3u playlist') - + for record in records_list: record_id = record.getAttribute('record_id') channel_id = record.getAttribute('epg_id') @@ -329,18 +336,18 @@ def handle(self, connection, headers_only=False): if channel.getAttribute('epg_id') == channel_id: channel_name = channel.getAttribute('name') logo = channel.getAttribute('logo') - + if channel_name != '': name = '(' + channel_name + ') ' + name if logo != '' and config.fullpathlogo: logo = P2pproxy.TTVU + logo - + playlistgen.addItem({'group': channel_name, 'name': n, 'url': record_id, 'logo': logo, 'tvg': ''}) P2pproxy.logger.debug('Exporting') exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True, fmt=self.get_param('fmt')) exported = exported.encode('utf-8') - + connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') connection.send_header('Content-Length', str(len(exported))) @@ -369,7 +376,7 @@ def handle(self, connection, headers_only=False): connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') - + if headers_only: connection.end_headers() else: @@ -386,7 +393,7 @@ def handle(self, connection, headers_only=False): connection.end_headers() connection.wfile.write("logobase = '" + P2pproxy.TTVU + "'\n") connection.wfile.write("logomap = {\n") - + for channel in translations_list: name = channel.getAttribute('name').encode('utf-8') logo = channel.getAttribute('logo').encode('utf-8') @@ -395,7 +402,7 @@ def handle(self, connection, headers_only=False): connection.wfile.write(",\n") else: connection.wfile.write("\n") - + connection.wfile.write("}\n") def get_param(self, key): @@ -406,9 +413,9 @@ def get_param(self, key): def get_date_param(self): d = self.get_param('date') - + if not d: - return date.today() + return date.today() else: return self.parse_date(d) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py old mode 100644 new mode 100755 index 60c63de..2fe346a --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -33,10 +33,10 @@ def handle(self, connection, headers_only=False): for c in self.stuff.clientcounter.clients[i]: connection.wfile.write('') if c.channelIcon: - connection.wfile.write(' ') + connection.wfile.write(' ') if c.channelName: connection.wfile.write(c.channelName.encode('UTF8')) else: connection.wfile.write(i) - connection.wfile.write('' + c.handler.clientip + '') + connection.wfile.write(' : ' + c.handler.clientip + '') connection.wfile.write('') diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py old mode 100644 new mode 100755 index 10547f9..db44b48 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -36,14 +36,15 @@ class TorrentTvApi(object): 11: 'Региональные', 12: 'Религиозные' } - + API_URL = 'http://1ttvapi.top/v3/' - + def __init__(self, email, password, maxIdle): self.email = email self.password = password self.maxIdle = maxIdle self.session = None + self.allTranslations = None self.lastActive = 0.0 self.lock = threading.RLock() self.log = logging.getLogger("TTV API") @@ -63,7 +64,7 @@ def auth(self): self.lastActive = time.time() self.log.debug("Reusing previous session: " + self.session) return self.session - + self.log.debug("Creating new session") self.session = None req = TorrentTvApi.API_URL + 'auth.php?typeresult=json&username=' + self.email + '&password=' + self.password + '&application=tsproxy&guid=' + str(random.randint(100000000,199999999)) @@ -83,7 +84,7 @@ def translations(self, translation_type, raw=False): :param raw: if True returns unprocessed data :return: translations list """ - + if raw: try: res = self._xmlresult('translation_list.php', '&type=' + translation_type) @@ -108,7 +109,7 @@ def records(self, channel_id, date, raw=False): :return: records list """ date = date.replace('-0', '-') - + if raw: try: res = self._xmlresult('arc_records.php', '&epg_id=' + channel_id + '&date=' + date) @@ -130,7 +131,7 @@ def archive_channels(self, raw=False): :param raw: if True returns unprocessed data :return: archive channels list """ - + if raw: try: res = self._xmlresult('arc_list.php', '') @@ -150,13 +151,16 @@ def stream_source(self, channel_id): :param session: valid user session required :param channel_id: id of channel in translations list (see translations() method) - :return: type of stream and source + :return: type of stream and source and translation list """ - + res = self._checkedjsonresult('translation_stream.php', '&channel_id=' + channel_id) stream_type = res['type'] source = res['source'] - return stream_type.encode('utf-8'), source.encode('utf-8') + allTranslations = self.allTranslations + if not allTranslations: + self.allTranslations = allTranslations = self.translations('all') + return stream_type.encode('utf-8'), source.encode('utf-8'), allTranslations def archive_stream_source(self, record_id): """ @@ -166,7 +170,7 @@ def archive_stream_source(self, record_id): :param record_id: id of record in records list (see records() method) :return: type of stream and source """ - + res = self._checkedjsonresult('arc_stream.php', '&record_id=' + record_id) stream_type = res['type'] source = res['source'] @@ -252,4 +256,5 @@ def _xmlresult(self, request, params): def _resetSession(self): with self.lock: self.session = None + self.allTranslations = None self.auth() From c68d9b94d342375d021f9d076c1ee6961269bd3e Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Thu, 19 May 2016 18:29:00 +0300 Subject: [PATCH 54/95] Fixed zombie connections caused by GreenletExit exception. Added configuration for detecting fake requests. Added configuration for changing channels name, group etc. --- aceconfig.py | 16 ++++++++- acehttp.py | 53 ++++++++++++---------------- plugins/config/playlist.py | 36 +++++++++++++++---- plugins/modules/PlaylistGenerator.py | 16 ++++++--- plugins/torrenttv_plugin.py | 12 +++---- 5 files changed, 85 insertions(+), 48 deletions(-) diff --git a/aceconfig.py b/aceconfig.py index 0b907d0..b721f93 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -194,4 +194,18 @@ class AceConfig(acedefconfig.AceDefConfig): logdatefmt='%d.%m %H:%M:%S' # Full path to a log file logfile = None - \ No newline at end of file + + # This method is used to detect fake requests. Some players send such + # requests in order to detect the MIME type and/or check the stream availability. + @staticmethod + def isFakeRequest(path, params, headers): + useragent = headers.get('User-Agent') + + if not useragent: + return False + elif useragent in AceConfig.fakeuas: + return True + elif useragent == 'Lavf/55.33.100' and not headers.has_key('Range'): + return True + elif useragent == 'GStreamer souphttpsrc (compatible; LG NetCast.TV-2013) libsoup/2.34.2' and headers.get('icy-metadata') != '1': + return True diff --git a/acehttp.py b/acehttp.py index e7ca9b9..0f763a0 100755 --- a/acehttp.py +++ b/acehttp.py @@ -78,9 +78,12 @@ def dieWithError(self, errorcode=500): ''' logging.warning("Dying with error") if self.connected: - self.send_error(errorcode) - self.end_headers() - self.closeConnection() + try: + self.send_error(errorcode) + self.end_headers() + self.closeConnection() + except: + pass def proxyReadWrite(self): ''' @@ -122,11 +125,13 @@ def proxyReadWrite(self): if data and self.connected: self.wfile.write(data) else: - logger.warning("Video connection closed") + if self.connected: + logger.warning("Video connection closed") break except SocketException: # Video connection dropped - logger.warning("Video connection dropped") + if self.connected: + logger.warning("Video connection dropped") finally: self.video.close() self.client.destroy() @@ -146,16 +151,12 @@ def hangDetector(self): finally: logger.debug("Client disconnected") client = self.client - if client: - self.client.destroy() + video = self.video - try: - self.requestgreenlet.kill() - except: - pass - finally: - gevent.sleep() - return + if client: + client.destroy() + if video: + video.close() def do_HEAD(self): return self.do_GET(headers_only=True) @@ -217,6 +218,7 @@ def do_GET(self, headers_only=False): def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=None): logger = logging.getLogger('handleRequest') + logger.debug("Headers:\n" + str(self.headers)) self.requrl = urlparse.urlparse(self.path) self.reqparams = urlparse.parse_qs(self.requrl.query) self.path = self.requrl.path[:-1] if self.requrl.path.endswith('/') else self.requrl.path @@ -240,13 +242,13 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No self.dieWithError(503) # 503 Service Unavailable return - # Pretend to work fine with Fake UAs or HEAD request. - useragent = self.headers.get('User-Agent') - fakeua = useragent and useragent in AceConfig.fakeuas - if headers_only or fakeua: - if fakeua: - logger.debug("Got fake UA: " + self.headers.get('User-Agent')) + # Pretend to work fine with Fake or HEAD request. + if headers_only or AceConfig.isFakeRequest(self.path, self.reqparams, self.headers): # Return 200 and exit + if headers_only: + logger.debug("Sending headers and closing connection") + else: + logger.debug("Fake request - closing connection") self.send_response(200) self.send_header("Content-Type", "video/mpeg") self.end_headers() @@ -262,6 +264,7 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No self.params.append('0') self.url = None + self.video = None self.path_unquoted = urllib2.unquote(self.splittedpath[2]) contentid = self.getCid(self.reqtype, self.path_unquoted) cid = contentid if contentid else self.path_unquoted @@ -270,16 +273,6 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No self.vlcid = urllib2.quote(cid, '') shouldStart = AceStuff.clientcounter.add(cid, self.client) == 1 - # Send fake headers if this User-Agent is in fakeheaderuas tuple - if fakeua: - logger.debug( - "Sending fake headers for " + useragent) - self.send_response(200) - self.send_header("Content-Type", "video/mpeg") - self.end_headers() - # Do not send real headers at all - self.headerssent = True - try: # Initializing AceClient if shouldStart: diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py index 5261ac9..0c26207 100755 --- a/plugins/config/playlist.py +++ b/plugins/config/playlist.py @@ -1,7 +1,31 @@ -# Default playlist format -m3uemptyheader = '#EXTM3U\n' -m3uheader = '#EXTM3U url-tvg="http://1ttvapi.top/ttv.xmltv.xml.gz"\n' -# If you need the #EXTGRP field put this #EXTGRP:%(group)s\n after %(name)s\n. -m3uchanneltemplate = \ - '#EXTINF:-1 group-title="%(group)s" tvg-name="%(tvg)s" tvg-id="%(tvgid)s" tvg-logo="%(logo)s",%(name)s\n%(url)s\n' +class PlaylistConfig(): + + # Default playlist format + m3uemptyheader = '#EXTM3U\n' + m3uheader = '#EXTM3U url-tvg="http://1ttvapi.top/ttv.xmltv.xml.gz"\n' + # If you need the #EXTGRP field put this #EXTGRP:%(group)s\n after %(name)s\n. + m3uchanneltemplate = \ + '#EXTINF:-1 group-title="%(group)s" tvg-name="%(tvg)s" tvg-id="%(tvgid)s" tvg-logo="%(logo)s",%(name)s\n%(url)s\n' + + # Channel names mapping. You may use this to rename channels. + # Examples: + # m3uchannelnames[u'Canal+ HD (France)'] = u'Canal+ HD' + # m3uchannelnames[u'Sky Sport 1 HD (Italy)'] = u'Sky Sport 1 HD' + m3uchannelnames = dict() + + # This method can be used to change a channel info such as name, group etc. + # The following fields can be changed: + # + # name - channel name + # url - channel URL + # tvg - channel tvg name + # tvgid - channel tvg id + # group - channel group + # logo - channel logo + @staticmethod + def changeItem(item): + name = PlaylistConfig.m3uchannelnames.get(item['name']) + + if name: + item['name'] = name diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py index 7dd0446..478dd43 100755 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -5,15 +5,20 @@ ''' import re import urllib2 -import plugins.config.playlist as config +from plugins.config.playlist import PlaylistConfig as config class PlaylistGenerator(object): - def __init__(self, m3uemptyheader=config.m3uemptyheader, m3uheader=config.m3uheader, m3uchanneltemplate=config.m3uchanneltemplate): + def __init__(self, + m3uemptyheader=config.m3uemptyheader, + m3uheader=config.m3uheader, + m3uchanneltemplate=config.m3uchanneltemplate, + changeItem = config.changeItem): self.itemlist = list() self.m3uemptyheader = m3uemptyheader self.m3uheader = m3uheader self.m3uchanneltemplate = m3uchanneltemplate + self.changeItem = changeItem def addItem(self, itemdict): ''' @@ -32,15 +37,17 @@ def _generatem3uline(self, item): ''' Generates EXTINF line with url ''' + self.changeItem(item) + if not item.has_key('tvg'): - item['tvg'] = '' + item['tvg'] = item.get('name').replace(' ', '_') if not item.has_key('tvgid'): item['tvgid'] = '' if not item.has_key('group'): item['group'] = '' if not item.has_key('logo'): item['logo'] = '' - + return self.m3uchanneltemplate % item def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, process_url=True, header=None, fmt=None): @@ -63,7 +70,6 @@ def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive for i in self.itemlist: item = i.copy() item['name'] = item['name'].replace('"', "'").replace(',', '.') - item['tvg'] = item.get('tvg', '') if item.has_key('tvg') else item.get('name').replace(' ', '_') url = item['url']; if process_url: diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 5fee671..e54e675 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -118,13 +118,13 @@ def handle(self, connection, headers_only=False): name = urllib2.unquote(url.path[19:-4]).decode('UTF8') url = self.channels[name] if url.startswith('acestream://'): - connection.path = '/pid/' + url[12:] + '/stream.mp4' - connection.splittedpath = connection.path.split('/') - connection.reqtype = 'pid' + connection.path = '/pid/' + url[12:] + '/stream.mp4' + connection.splittedpath = connection.path.split('/') + connection.reqtype = 'pid' else: - connection.path = '/torrent/' + urllib2.quote(url, '') + '/stream.mp4' - connection.splittedpath = connection.path.split('/') - connection.reqtype = 'torrent' + connection.path = '/torrent/' + urllib2.quote(url, '') + '/stream.mp4' + connection.splittedpath = connection.path.split('/') + connection.reqtype = 'torrent' play = True elif self.etag == connection.headers.get('If-None-Match'): self.logger.debug('ETag matches - returning 304') From 894f821dec88318c05cbe30a08e22b0920449004 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 20 May 2016 20:12:37 +0300 Subject: [PATCH 55/95] Minor enhancements --- acehttp.py | 5 +++-- plugins/config/playlist.py | 6 +++--- plugins/torrenttv_plugin.py | 9 ++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/acehttp.py b/acehttp.py index 0f763a0..523e082 100755 --- a/acehttp.py +++ b/acehttp.py @@ -72,11 +72,12 @@ def closeConnection(self): except: pass - def dieWithError(self, errorcode=500): + def dieWithError(self, errorcode=500, logmsg='Dying with error', loglevel=logging.WARN): ''' Close connection with error ''' - logging.warning("Dying with error") + if logmsg: + logging.log(loglevel, logmsg) if self.connected: try: self.send_error(errorcode) diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py index 0c26207..ad1e350 100755 --- a/plugins/config/playlist.py +++ b/plugins/config/playlist.py @@ -1,4 +1,4 @@ - +# -*- coding: utf-8 -*- class PlaylistConfig(): # Default playlist format @@ -10,8 +10,8 @@ class PlaylistConfig(): # Channel names mapping. You may use this to rename channels. # Examples: - # m3uchannelnames[u'Canal+ HD (France)'] = u'Canal+ HD' - # m3uchannelnames[u'Sky Sport 1 HD (Italy)'] = u'Sky Sport 1 HD' + # m3uchannelnames['Canal+ HD (France)'] = 'Canal+ HD' + # m3uchannelnames['Sky Sport 1 HD (Italy)'] = 'Sky Sport 1 HD' m3uchannelnames = dict() # This method can be used to change a channel info such as name, group etc. diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index e54e675..9c42c31 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -112,12 +112,15 @@ def handle(self, connection, headers_only=False): if url.path.startswith('/torrenttv/channel/'): if not url.path.endswith('.mp4'): - connection.dieWithError() + connection.dieWithError(404, 'Invalid path: ' + url.path, logging.DEBUG) return name = urllib2.unquote(url.path[19:-4]).decode('UTF8') - url = self.channels[name] - if url.startswith('acestream://'): + url = self.channels.get(name) + if not url: + connection.dieWithError(404, 'Unknown channel: ' + name, logging.DEBUG) + return + elif url.startswith('acestream://'): connection.path = '/pid/' + url[12:] + '/stream.mp4' connection.splittedpath = connection.path.split('/') connection.reqtype = 'pid' From a37f88f75ac87adf1751d357f63880d9d64ebb5a Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 21 May 2016 18:04:38 +0300 Subject: [PATCH 56/95] Fixed unicode channels renaming. --- plugins/config/playlist.py | 15 +++++++++++---- plugins/torrenttv_plugin.py | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py index ad1e350..b91dbbc 100755 --- a/plugins/config/playlist.py +++ b/plugins/config/playlist.py @@ -25,7 +25,14 @@ class PlaylistConfig(): # logo - channel logo @staticmethod def changeItem(item): - name = PlaylistConfig.m3uchannelnames.get(item['name']) - - if name: - item['name'] = name + if len(PlaylistConfig.m3uchannelnames) > 0: + name = item['name'] + + if isinstance(name, str): + name = PlaylistConfig.m3uchannelnames.get(name) + if name: + item['name'] = name + elif isinstance(name, unicode): + name = PlaylistConfig.m3uchannelnames.get(name.encode('utf8')) + if name: + item['name'] = name.decode('utf8') diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 9c42c31..246b3c7 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -59,7 +59,6 @@ def downloadPlaylist(self): name = encname.decode('UTF-8') logo = config.logomap.get(name) url = itemdict['url'] - self.playlist.addItem(itemdict) if logo: itemdict['logo'] = logo @@ -67,7 +66,8 @@ def downloadPlaylist(self): if (url.startswith('acestream://')) or (url.startswith('http://') and url.endswith('.acelive')): self.channels[name] = url itemdict['url'] = urllib2.quote(encname, '') + '.mp4' - + + self.playlist.addItem(itemdict) m.update(encname) self.etag = '"' + m.hexdigest() + '"' From 31b6b2e46692c2ead7a33070d6142f3f5ac06d2d Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 13 Jun 2016 22:19:44 +0300 Subject: [PATCH 57/95] Fixed transcoding --- aceconfig.py | 1 + acehttp.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/aceconfig.py b/aceconfig.py index b721f93..ac55b88 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -140,6 +140,7 @@ class AceConfig(acedefconfig.AceDefConfig): # contains the fmt=mp2. It means that the 'mp2' command will be used for # transcoding. You may add any number of commands to this dictionary. transcodecmd = dict() + # transcodecmd['100k'] = 'ffmpeg -i - -c:a copy -b 100k -f mpegts -' # transcodecmd['mp2'] = 'ffmpeg -i - -c:a mp2 -c:v mpeg2video -f mpegts -qscale:v 2 -'.split() # transcodecmd['mkv'] = 'ffmpeg -i - -c:a copy -c:v copy -f matroska -'.split() # transcodecmd['default'] = 'ffmpeg -i - -c:a copy -c:v copy -f mpegts -'.split() diff --git a/acehttp.py b/acehttp.py index 523e082..698296c 100755 --- a/acehttp.py +++ b/acehttp.py @@ -479,7 +479,7 @@ def handle(self, shouldStart, url, fmt=None): if AceConfig.transcodecmd.has_key(fmt): stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL transcoder = psutil.Popen(AceConfig.transcodecmd[fmt], bufsize=AceConfig.readchunksize, - stdin=PIPE, stdout=self.handler.wfile, stderr=stderr) + stdin=PIPE, stdout=self.handler.wfile, stderr=stderr, shell=True) out = transcoder.stdin else: transcoder = None From 3f1e5f17410071bd44217c65a7d8471aa33ef7c5 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Tue, 14 Jun 2016 00:22:20 +0300 Subject: [PATCH 58/95] Implemented playlist sorting. --- plugins/config/playlist.py | 45 ++++++++++++++++++++------ plugins/modules/PlaylistGenerator.py | 47 +++++++++++++++++----------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py index b91dbbc..ccd2304 100755 --- a/plugins/config/playlist.py +++ b/plugins/config/playlist.py @@ -14,6 +14,14 @@ class PlaylistConfig(): # m3uchannelnames['Sky Sport 1 HD (Italy)'] = 'Sky Sport 1 HD' m3uchannelnames = dict() + # Similar to m3uchannelnames but for groups + m3ugroupnames = dict() + + # Playlist sorting options. + sort = False + sortByName = False + sortByGroupName = False + # This method can be used to change a channel info such as name, group etc. # The following fields can be changed: # @@ -25,14 +33,31 @@ class PlaylistConfig(): # logo - channel logo @staticmethod def changeItem(item): - if len(PlaylistConfig.m3uchannelnames) > 0: - name = item['name'] + PlaylistConfig._changeItemByDict(item, 'name', PlaylistConfig.m3uchannelnames) + PlaylistConfig._changeItemByDict(item, 'group', PlaylistConfig.m3ugroupnames) + + @staticmethod + def _changeItemByDict(item, key, replacementsDict): + if len(replacementsDict) > 0: + value = item[key] - if isinstance(name, str): - name = PlaylistConfig.m3uchannelnames.get(name) - if name: - item['name'] = name - elif isinstance(name, unicode): - name = PlaylistConfig.m3uchannelnames.get(name.encode('utf8')) - if name: - item['name'] = name.decode('utf8') + if isinstance(value, str): + value = replacementsDict.get(value) + if value: + item[key] = value + elif isinstance(value, unicode): + value = replacementsDict.get(value.encode('utf8')) + if value: + item[key] = value.decode('utf8') + + # This comparator is used for the playlist sorting. + @staticmethod + def compareItems(i1, i2): + result = -1 + if PlaylistConfig.sortByGroupName: + result = cmp(i1.get('group', ''), i2.get('group', '')) + if result != 0: + return result + if PlaylistConfig.sortByName: + result = cmp(i1.get('name', ''), i2.get('name', '')) + return result diff --git a/plugins/modules/PlaylistGenerator.py b/plugins/modules/PlaylistGenerator.py index 478dd43..6bba708 100755 --- a/plugins/modules/PlaylistGenerator.py +++ b/plugins/modules/PlaylistGenerator.py @@ -10,15 +10,17 @@ class PlaylistGenerator(object): def __init__(self, - m3uemptyheader=config.m3uemptyheader, - m3uheader=config.m3uheader, + m3uemptyheader=config.m3uemptyheader, + m3uheader=config.m3uheader, m3uchanneltemplate=config.m3uchanneltemplate, - changeItem = config.changeItem): + changeItem=config.changeItem, + comparator=config.compareItems if config.sort else None): self.itemlist = list() self.m3uemptyheader = m3uemptyheader self.m3uheader = m3uheader self.m3uchanneltemplate = m3uchanneltemplate self.changeItem = changeItem + self.comparator = comparator def addItem(self, itemdict): ''' @@ -37,19 +39,20 @@ def _generatem3uline(self, item): ''' Generates EXTINF line with url ''' - self.changeItem(item) - - if not item.has_key('tvg'): - item['tvg'] = item.get('name').replace(' ', '_') - if not item.has_key('tvgid'): - item['tvgid'] = '' - if not item.has_key('group'): - item['group'] = '' - if not item.has_key('logo'): - item['logo'] = '' - return self.m3uchanneltemplate % item + def _changeItems(self): + for item in self.itemlist: + self.changeItem(item) + if not item.has_key('tvg'): + item['tvg'] = item.get('name').replace(' ', '_') + if not item.has_key('tvgid'): + item['tvgid'] = '' + if not item.has_key('group'): + item['group'] = '' + if not item.has_key('logo'): + item['logo'] = '' + def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, process_url=True, header=None, fmt=None): ''' Exports m3u playlist @@ -66,8 +69,14 @@ def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive itemlist = self.m3uemptyheader else: itemlist = header + + self._changeItems() + if self.comparator: + items = sorted(self.itemlist, cmp=self.comparator) + else: + items=self.itemlist - for i in self.itemlist: + for i in items: item = i.copy() item['name'] = item['name'].replace('"', "'").replace(',', '.') url = item['url']; @@ -77,16 +86,16 @@ def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive item['url'] = re.sub('^(http.+)$', lambda match: 'http://' + hostport + path + '/torrent/' + \ urllib2.quote(match.group(0), '') + '/stream.mp4', url, flags=re.MULTILINE) - if url == item['url']: # For PIDs + if url == item['url']: # For PIDs item['url'] = re.sub('^(acestream://)?(?P[0-9a-f]{40})$', 'http://' + hostport + path + '/pid/\\g/stream.mp4', url, flags=re.MULTILINE) - if archive and url == item['url']: # For archive channel id's + if archive and url == item['url']: # For archive channel id's item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/archive/play?id=' + match.group(0), url, flags=re.MULTILINE) - if not archive and url == item['url']: # For channel id's + if not archive and url == item['url']: # For channel id's item['url'] = re.sub('^([0-9]+)$', lambda match: 'http://' + hostport + path + '/channels/play?id=' + match.group(0), url, flags=re.MULTILINE) - if url == item['url']: # For channel names + if url == item['url']: # For channel names item['url'] = re.sub('^([^/]+)$', lambda match: 'http://' + hostport + path + '/' + match.group(0), url, flags=re.MULTILINE) From 9da639cf6146e3be5f1a97d8b416bc5aa87681a0 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 15 Jun 2016 00:12:00 +0300 Subject: [PATCH 59/95] Added support for zone id. --- plugins/config/p2pproxy.py | 6 +++++- plugins/p2pproxy_plugin.py | 2 +- plugins/torrenttv_api.py | 13 +++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/plugins/config/p2pproxy.py b/plugins/config/p2pproxy.py index a45c00a..9b2da83 100644 --- a/plugins/config/p2pproxy.py +++ b/plugins/config/p2pproxy.py @@ -37,4 +37,8 @@ tvgshift = 0 # Format of the tvg-id tag or empty string -tvgid='ttv%(id)s' \ No newline at end of file +tvgid='ttv%(id)s' + +# Zone id - MSK, SPB, SAM, etc. +# For more details see http://api.torrent-tv.ru/v3/api_v3.html#toc-35- +zoneid=None diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 62ba435..f14cbd8 100755 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -39,7 +39,7 @@ class P2pproxy(AceProxyPlugin): def __init__(self, AceConfig, AceStuff): super(P2pproxy, self).__init__(AceConfig, AceStuff) self.params = None - self.api = TorrentTvApi(config.email, config.password, config.sessiontimeout) + self.api = TorrentTvApi(config.email, config.password, config.sessiontimeout, config.zoneid) def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Handling request') diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py index db44b48..20abb68 100755 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -39,10 +39,11 @@ class TorrentTvApi(object): API_URL = 'http://1ttvapi.top/v3/' - def __init__(self, email, password, maxIdle): + def __init__(self, email, password, maxIdle, zoneid=None): self.email = email self.password = password self.maxIdle = maxIdle + self.zoneid = zoneid self.session = None self.allTranslations = None self.lastActive = 0.0 @@ -84,18 +85,22 @@ def translations(self, translation_type, raw=False): :param raw: if True returns unprocessed data :return: translations list """ + + query = '&type=' + translation_type + if self.zoneid: + query += '&zone_id=' + self.zoneid if raw: try: - res = self._xmlresult('translation_list.php', '&type=' + translation_type) + res = self._xmlresult('translation_list.php', query) self._checkxml(res) return res except TorrentTvApiException: - res = self._xmlresult('translation_list.php', '&type=' + translation_type) + res = self._xmlresult('translation_list.php', query) self._checkxml(res) return res else: - res = self._checkedxmlresult('translation_list.php', '&type=' + translation_type) + res = self._checkedxmlresult('translation_list.php', query) return res.getElementsByTagName('channel') def records(self, channel_id, date, raw=False): From 261b3a3a96827b7ca64926ea631b6f1c332cc978 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 19 Jun 2016 18:35:28 +0300 Subject: [PATCH 60/95] Added channel name to tvg name mappings --- plugins/config/playlist.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/config/playlist.py b/plugins/config/playlist.py index ccd2304..c188b95 100755 --- a/plugins/config/playlist.py +++ b/plugins/config/playlist.py @@ -14,9 +14,13 @@ class PlaylistConfig(): # m3uchannelnames['Sky Sport 1 HD (Italy)'] = 'Sky Sport 1 HD' m3uchannelnames = dict() - # Similar to m3uchannelnames but for groups + # Similar to m3uchannelnames but for groups. m3ugroupnames = dict() + # Channel name to tvg name mappings. + m3utvgnames = dict() + # m3utvgnames['Channel name'] = 'Tvg_name' + # Playlist sorting options. sort = False sortByName = False @@ -35,20 +39,23 @@ class PlaylistConfig(): def changeItem(item): PlaylistConfig._changeItemByDict(item, 'name', PlaylistConfig.m3uchannelnames) PlaylistConfig._changeItemByDict(item, 'group', PlaylistConfig.m3ugroupnames) + PlaylistConfig._changeItemByDict(item, 'name', PlaylistConfig.m3utvgnames, 'tvg') @staticmethod - def _changeItemByDict(item, key, replacementsDict): + def _changeItemByDict(item, key, replacementsDict, setKey=None): if len(replacementsDict) > 0: value = item[key] + if not setKey: + setKey = key if isinstance(value, str): value = replacementsDict.get(value) if value: - item[key] = value + item[setKey] = value elif isinstance(value, unicode): value = replacementsDict.get(value.encode('utf8')) if value: - item[key] = value.decode('utf8') + item[setKey] = value.decode('utf8') # This comparator is used for the playlist sorting. @staticmethod From 5915626271f51051c93b0955f63385ca46289d5f Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 19 Aug 2016 19:25:34 +0300 Subject: [PATCH 61/95] Enhanced the channels group filter --- plugins/p2pproxy_plugin.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index f14cbd8..33c3ed4 100755 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -106,10 +106,18 @@ def handle(self, connection, headers_only=False): connection.end_headers() return - param_group = self.get_param('group') + param_group = self.params.get('group') param_filter = self.get_param('filter') if not param_filter: param_filter = 'all' # default filter + if param_group: + if 'all' in param_group: + param_group = None + else: + tmp = [] + for g in param_group: + tmp += g.split(',') + param_group = tmp translations_list = self.api.translations(param_filter) @@ -118,7 +126,7 @@ def handle(self, connection, headers_only=False): for channel in translations_list: group_id = channel.getAttribute('group') - if param_group and param_group != 'all' and param_group != group_id: # filter channels by group + if param_group and not group_id in param_group: # filter channels by group continue name = channel.getAttribute('name') From c47af7fb6e43dc48b93e9a0a2ca988864075615a Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 19 Aug 2016 20:07:08 +0300 Subject: [PATCH 62/95] Fixed support for the fmt parameter --- plugins/torrenttv_plugin.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 246b3c7..eab25fc 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -107,15 +107,16 @@ def handle(self, connection, headers_only=False): return url = urlparse.urlparse(connection.path) + path = url.path[0:-1] if url.path.endswith('/') else url.path params = urlparse.parse_qs(url.query) fmt = params['fmt'][0] if params.has_key('fmt') else None - if url.path.startswith('/torrenttv/channel/'): - if not url.path.endswith('.mp4'): - connection.dieWithError(404, 'Invalid path: ' + url.path, logging.DEBUG) + if path.startswith('/torrenttv/channel/'): + if not path.endswith('.mp4'): + connection.dieWithError(404, 'Invalid path: ' + path, logging.DEBUG) return - name = urllib2.unquote(url.path[19:-4]).decode('UTF8') + name = urllib2.unquote(path[19:-4]).decode('UTF8') url = self.channels.get(name) if not url: connection.dieWithError(404, 'Unknown channel: ' + name, logging.DEBUG) @@ -138,7 +139,7 @@ def handle(self, connection, headers_only=False): else: hostport = connection.headers['Host'] path = '' if len(self.channels) == 0 else '/torrenttv/channel' - add_ts = True if url.path.endswith('/ts') else False + add_ts = True if path.endswith('/ts') else False header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' % (config.tvgurl, config.tvgshift) exported = self.playlist.exportm3u(hostport, path, add_ts=add_ts, header=header, fmt=fmt) @@ -150,6 +151,6 @@ def handle(self, connection, headers_only=False): connection.end_headers() if play: - connection.handleRequest(headers_only, name, config.logomap.get(name)) + connection.handleRequest(headers_only, name, config.logomap.get(name), fmt=fmt) elif not headers_only: connection.wfile.write(exported) From e686825e5b078a15efa4c7438469f661fc73afb0 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 19 Aug 2016 20:17:50 +0300 Subject: [PATCH 63/95] Updated logos --- plugins/config/torrenttv.py | 200 +++++++++++++++--------------------- 1 file changed, 84 insertions(+), 116 deletions(-) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 5b67137..8de8524 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -22,22 +22,20 @@ # Channel logos mapping logobase = 'http://torrent-tv.ru/uploads/' logomap = { + u'0x0 Fireplace HD': logobase + 'H1VboxDJC7sE7x3nKXoYT0X5r4LIqD.png', + u'0x0 Music HD': logobase + 'hFj4tnC5uqAgpod3doHnJGZxgZXaiP.png', u'1 HD': logobase + 'FtLnmUwjG18XJFEKYvLKjwUq1gwHVZ.png', - u'1 Балтийский музыкальный': logobase + 'qOr6q3gTZcbIzbUUduk3pIp2wh6uG3.png', - u'1 канал (Израиль)': logobase + 'jdPZiDGCTrwqm9DOyjWqhx8fsZNnzU.png', u'1+1': logobase + 'omm2Xc8xSVIT6Od6ca4QqMrEXw3jaK.png', u'1+1 International': logobase + 'o6wtkD3LF7NT4yADrcoBrXVWDDrvgV.png', u'100% News': logobase + '9yEWvPmTcFS8lyQ5NjJ7vbYOa3bx1W.png', u'112 Украина': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'112 Украина HD': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'12 Канал (Омск)': logobase + '6DFMdGjXUjvzyk3B5ltbEd8mWVxAcD.png', - u'2 канал (Израиль)': logobase + 'b8SuI3XcVDLAM39jRemlcwcgb6Snuo.png', u'2+2': logobase + 'XHXBC3ghvhh100BNXylSgJLx5FVQgD.png', u'24 Док': logobase + 'H1UXBai10DjYfScfv1sNAILV9EPDer.png', u'24 Украина': logobase + 'XfKEdfsy4S1zbE8n2tB1tNNe9IkrRP.png', u'2x2': logobase + 'hTpJUV15GSTxZ5kJQGLcn42kCzKyEH.png', u'2x2 (+2)': logobase + 'ZPOhZle6vrDaulo2KMmyrCkkkLn7Ci.png', - u'2x2 (+5)': logobase + 'WMlvhNmhzmk2JgKct8Oa5FcWHmR7fZ.png', u'360 градусов': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', u'360 градусов HD': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', u'365 Дней': logobase + '0IZrVwoxtmjtgnWu5Dj4Hb8FRc8NIX.png', @@ -46,53 +44,38 @@ u'5 канал (Украина)': logobase + '9La0uS6S8rMKr0BOh6vSCQLNiCqN7N.png', u'8 канал': logobase + 'wKPUJjcpZIfI5zBSZwNayOlv63zRNV.png', u'9 волна': logobase + '8zDeTSBhsmJDbXo9dxHA1c9mDOP9sP.png', - u'A-One': logobase + 'buHzdmouswQyLnhzwqnPUHdS9BMHTw.png', + u'9 канал Израиль': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', u'A-One UA': logobase + '8rEKNCXhKeWnUvGhWCsrb2RN5FMqJi.png', u'Al Jazeera English': logobase + 'B1AC3jl0CY8u4qxO0aIEHVMFQFvBUu.png', u'AMC': logobase + 'rozrC8ZPYA1YGajyow35doeVcaQzpa.png', u'Amedia 1': logobase + 'jT3vAEOG5jTd2t8GcC797Bw5W0kSl9.png', u'Amedia 2': logobase + 'fAvxTQbWu0DAcMkqej0m73KohAcQJw.png', - u'Amedia Hit HD': logobase + 'ujYSHmxPViAzjm4yc1YPi42hju7vcq.png', + u'Amedia Premium': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Amedia Premium HD': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', - u'Amedia Premium HD (SD)': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Ani': logobase + 'vui1cRrE05CZv1N9Qb20jJ6mTFOJue.png', + u'Animal Family HD': logobase + '58VN85gBIna4Ko2K2uNeG6ZrfFNTLK.png', u'Animal Planet': logobase + '45.png', u'Animаl Planеt HD': logobase + '9HZGan5rQItVQOfnB91FGqyJXjoqYV.png', u'Anyday 3D': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', u'Anyday HD': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', - u'Arena Sport 1': logobase + 'EK2c3dIiDNuYQ9zdlASOTaJabNf4gI.png', - u'Arena Sport 2': logobase + 'QWlwDu4FRIilHsC6ZFR6BdFW8qtvnb.png', - u'Arena Sport 3': logobase + 'x31HEc9Vo87VdvF1eXQTl61ZXgq7HY.png', u'Armenia TV Satellite': logobase + 'M4UAMT6cEyL3zuim9fzy38vZRkIfKq.png', - u'ATR': logobase + 'uggqxQxxDTuz1P1mY4PZdlRcAKow3F.png', u'Az TV': logobase + 'FB2qxgNm8xTU6YiR2fY1jH4PIRR2SF.png', u'B4U Music UK': logobase + 'LlfDsq3FEU2D4P3lmj25fvl6zQnvNM.png', - u'BabyTV': logobase + 'BXLto7eBgsRpbGP6Cb8tgIfh4h5RiK.png', - u'Balticum Platinum': logobase + 'c0hXvCfIFAs4Nt4SSGmfZ5DMZYg4jJ.png', u'BBC One': logobase + 'ofuDCyM7MgzwFbzG2pyavFu6xeRHwp.png', - u'BBC Two': logobase + 'o7LyIuTJT2C7wJG77Zx5cZhBYP2L8B.png', + u'BBC Two HD': logobase + 'bbUzq7qIDlw2K8zk0oRHZzc4yHJkW6.png', u'BBC World News': logobase + '8cSYWwvq6BAzcGDjJODvCGQYGJ2c4S.png', + u'beIN Sports 1 HD': logobase + 'lqnLsL3LkmnVte1I8djXkbFEYFx91o.png', u'Bloomberg': logobase + 'OTsoNZRT8xjXz5nnTICPCQRvNLjBal.png', u'Blue Hustler': logobase + 'dFDAUzpGhQFOyHz59iyi7gsGHHMQbj.png', - u'Bollywood HD': logobase + 'HXzWxtMxmXkrpgr88Kh8Z1A8F6o5dK.png', u'Boomerang': logobase + 'tsP2U3zkp5o8B0TD6luRLg0leS9FvM.png', u'Brazzers TV Europe': logobase + 'OmVBp3Kz4Lx722dq2e1OxE26QSxrDA.png', u'Bridge TV': logobase + 'dPhBiaViIznwjeWU3pTbIXFmS3iJkU.png', u'Brodilo TV HD': logobase + '0pLPWiGqZjDe2O4vbPd8QL0qGsA9lq.png', - u'BT Sport 1': logobase + 'RNwudSF1Lys88WmIXhiS43g2urfknl.png', - u'BT Sport 2': logobase + 'NdEFtAJBS9EtR2rSEUZ1ZF15tQzH6y.png', u'BTV': logobase + '52ZaTijyyDOsKI7B1fYnUWliW6i2ah.png', u'Business': logobase + 'wvwXc2x8Bjev90GD6LO6t7TL2aKW1h.png', u'C More Tennis': logobase + 'ql4qy7gHN4GtWhcqfd97HqXUs8HXI9.png', u'C Music TV HD': logobase + 'YhtkJhsV8NKwm4FGWhQZDmYcW0TYQS.png', - u'Canal Fox ES': logobase + 'JiHKq6sUTY5tfS8NC2HtHmKgYgYHg3.png', - u'Canal TNT ES': logobase + 'Lk4pqg1KwHSEEutL9jQ1sTautSVtRB.png', - u'Canal+ Deportes': logobase + 'qoi5H4OujPPtCC1JJO1ozeyFLPqPk6.png', - u'Canal+ Deportes 2': logobase + '4EUoskZorTXBkMxeR8ykSpyQOErvgu.png', - u'Canal+ Futbol': logobase + '1En2KRyo8ywbMPWjiiri7TcEXJbqcE.png', - u'Canal+ Liga': logobase + '6GyapitmDBshZmCxXPJsQWJGhkcooJ.png', - u'Candy': logobase + 'YW8BJnImniuR7l1U85Khw31mX2XO0S.png', - u'Candyman': logobase + '8pCxUJ8TBWfvWCrIPN4a4Jimsv9onx.png', + u'Canal+ Sport HD (France)': logobase + 'I4vGNtOsYtkBnwOwFi94RHazK1ngqw.png', u'Cartoon Network': logobase + 'NTNQLLri3Hh9iqYjW7VEkFYJsTLjk9.png', u'CBS Drama': logobase + '1Mcq9jbl7qPeXpT0bbPTnf8aM3f7dq.png', u'CBS Reality': logobase + 'QbWaD2vNgLRLY0Hsccg3nf9azAXRld.png', @@ -100,51 +83,51 @@ u'CCTV News': logobase + 'TyWVFvKwV3lOYYXWVJSj8HMu5AodJr.png', u'CCTV Русский': logobase + 'DcaE92lKRSDFBPlYUnsNRauFLbotKu.png', u'CentoXCento TV': logobase + 'dPuPlqJtkLMRy7NmOhyHDfL0PJqYj4.png', - u'Chasse et Peche': logobase + '0X53uW9WfdUDtISMaT71SaPSf0rv3C.png', u'CNL': logobase + 'wwQ6u4lFXyaUDJLu2JOh6mtbkIz0Nf.png', u'CNN International': logobase + 'lIwbRbM3ve4ixhFXp75Oap9nzcO71G.png', - u'CT 1': logobase + 'sgPGofRHRzOlNb6dBsk5QGiCdzJewS.png', - u'CT 2': logobase + 'dFDWTyqFwS3roF7Jb3JGZIfm1htuUv.png', - u'CT 24': logobase + 'nylXA3Qw7fJt1ult4NUg7vTtpZobZz.png', u'Da Vinci Learning': logobase + 'Yl6p1IDDkZxxiUa3p2JxI66mIlOPns.png', u'Dange TV': logobase + 'ofmgiZlRmwOZtTaYtUtfErZS3ODDDM.png', u'Deluxe Music': logobase + '3VnQoAyJh3RZM88USPszL1TZIE5t0u.png', u'Deutsche Welle': logobase + 'RnqBDfde1HP4OkZhXWlKB1xkHi6Io9.png', - u'Diema Sport': logobase + 'Gep7g9hp9U5QLU9327KgsNFf9utzNy.png', - u'Diema Sport 2': logobase + '4xUpzBxK0sL6Hc9KQADA1LhmFbE9Yi.png', u'Digi Sport 1': logobase + 'gk0ajzoQjMHlBKM298kWjcfNy1P8ec.png', u'Digi Sport 2': logobase + 'x1HMd7tDoprxuZvq90Uj2Gb1eEOhTm.png', - u'Digi Sport 3': logobase + 'foaFOGGFFKo1T6HFFkPc8AmaJVgN8j.png', u'Discovery Channel': logobase + 'oKx1ImWVRT3AK3DHYWUVc71JZUkwu5.png', u'Discovery Channel HD': logobase + 'SmWnYlOvkJn8GzttT2UY0vmo8PYfMg.png', u'Discovery Science': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', - u'Discovery Science HD': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', u'Disney Channel': logobase + 'JxEjTeXwExjnxutQGKJBmMI85tpNqK.png', - u'Disney Channel (+2)': logobase + 'lPgnmTskftnw81t92AxtZSJJStKJ0K.png', + u'Djing Animation HD': logobase + 'ZA8pwcclRpzTflNKLqeZmDHCA7lHoX.png', + u'Djing Classic HD': logobase + 'fDCLK7k2nYRKPAD125JdHM5uuEM7bZ.png', + u'Djing Ibiza HD': logobase + 'aGqPHXL6MxB8j4GXldadnOR3r5D03f.png', + u'Djing Underground HD': logobase + 'ceUcUA6hsEV7tBVmSF1chE203B14M3.png', u'Dobro TV': logobase + 'tcHbtjkY9gYD4VqipgwLP2BYxgdrZb.png', + u'Dot Dance HD': logobase + 'Z8TDmebVzyNS57me2IO2rd2LaiNRM6.png', u'Dream TV': logobase + '3k3m3ElnUQ0UhX54qa7hOGbmNjKZSc.png', + u'DTX': logobase + 'bx7MosYUikfuwwIOzx2elT6Dh7h50I.png', u'English Club TV': logobase + 'Hf5RUQ91cEGtHoaK3GK6VIZlJ8Leql.png', u'Enter Film': logobase + '8FPA5SCEIj35fBO8yrULnefW4NzIzk.png', + u'Epoque': logobase + 'eULbHv3DyublWF2rAhw38DV63cV3V9.png', u'Eska Best Music TV HD': logobase + 'wI3e672FQZpD8yr8aIV8Q2fL15zENv.png', - u'Eska Music Vox TV OldsCool HD': logobase + '7LW3s6C3D3yeciqTY0CKXpEmL3Tb3w.png', + u'Eska OldsCool TV HD': logobase + '7LW3s6C3D3yeciqTY0CKXpEmL3Tb3w.png', u'Eska Party TV HD': logobase + 'MaX9is65hlDpRDwgOikGE2RqMRxn8G.png', u'Eska Rock TV HD': logobase + 'VU2POxFhYCM4XhFAtOp8Y4sdlbu32M.png', u'Eska TV': logobase + '1KX19DgurgoaSKNTTpjXCeSQr1epwv.png', u'Eska Wawa TV HD': logobase + 'r9d697qox4MFgypnVqeILYWKcfbwNc.png', - u'ESPN US HD': logobase + 'ak7iT40ScLotDyx6iTDcbITwyI0tB8.png', - u'EU Music': logobase + 'soPC6c06dpKRwWZFf1xV7yAUaqpVys.png', - u'Eureka HD': logobase + 'jSMxE4IBjH8pbRJgc2KRwgtq3qWOrj.png', + u'ETV 2 (Эстония)': logobase + 'HExQnCel4LeRvVFpvXj7hXfMXslkGE.png', + u'ETV pluss (Эстония)': logobase + 'cZBJJjYDyuuJHMQk3ZOogcDZYcu2SK.png', u'EuroNews': logobase + 'Vb3fP5gUK0q40WuzYeUhMT7RQmDg27.png', u'Europa Plus TV': logobase + 'PkatgpdmA4ArsgSG5shu0ZCQQ5RgMx.png', u'Eurosport 1': logobase + 'QA9jgUaQRrE4vMno04eM3aUrklXOce.png', u'Eurosport 1 HD': logobase + 'DpFTzUEA3y67Z6ObTPF4xH0XLNRAZm.png', u'Eurospоrt 2': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', - u'Eurospоrt 2 HD': logobase + '0tv5hm546AKIySs7cpj30LlCOcY0Mj.png', u'Exotica TV': logobase + 'K5hm3mURkkDaSc7RI5RDti5edynMGl.png', u'Extreme Sports': logobase + '21FhIqWK82JDPNuLTEIC9hSO2EHfks.png', + u'FAP TV': logobase + 'd5ulbooZicKw3aMLwn2Ho0yD9c46T2.png', + u'FAP TV 2': logobase + 'YVmUBY8cBPO8IoL4djlyeKCDbs6f0p.png', + u'FAP TV Anal': logobase + '1PVgf85c4DjIsiWEikpEHDV1j1m6ph.png', u'Fashion One HD': logobase + 'iPs2ptiBXm8h0KSnRmyqu45texHNig.png', u'Fashion TV': logobase + 'PSjqabjhYIBqcS8hUA8WNrZEjV4zZY.png', u'Fine Living': logobase + '22I1fK1aMCUgeYUCV0K3vwmlJKELZD.png', + u'Food Network': logobase + 'W68LEGlOOMPtN0qoGXvdkWhHBjKet6.png', u'Fox': logobase + 'XGC77wQNeEyaJ2z2mDipyIPsoF0xc1.png', u'Fox Life': logobase + 'rkksGUl3DstQSEyT26Q07hNCEwyNnd.png', u'France 24': logobase + 'pViXgcMjLnB5WyOoQJ7sBxhHo5fTSB.png', @@ -159,44 +142,41 @@ u'GlobalStar TV': logobase + 'Y4kNxgbAnPGCt753P73NzCIbcNz5lD.png', u'Gulli': logobase + '2IynBdmw3mmdXt01r8DXKxeor7STnw.png', u'HardLife TV': logobase + 'fHPc5oaRdIzKpHTqBFnHA4O2LVf91D.png', - u'HD Fashion': logobase + 'nEIXShrZ56g4IA4VKXReUBF32iYwKJ.png', + u'HD Fashion (SD)': logobase + 'nEIXShrZ56g4IA4VKXReUBF32iYwKJ.png', u'HD Life': logobase + 'jUteUS0xRGdvLyBVqNjowEUDkOjT0t.png', u'HD Media': logobase + 'woAI3zcytfbyiX0LBRToKzErJNy1qF.png', u'HD Media 3D': logobase + 'takNty6pirY7TeCRPX5VyUW1mZHHxI.png', u'HD Кино': logobase + 'iHVs7YvUVlUvMTnlma7GMpX5p0Tpy1.png', + u'Hello TV HD': logobase + 'o4WXPUTr5f2tuXFdjuLqgEg3qieGre.png', u'History Channel': logobase + '9cVifexiWW0qWDhhpnLNVydoZkeRqZ.png', u'History Channel HD': logobase + 'VFgU260pmiIyPxCzD3f8R7Yc6DXClH.png', u'Hit TV': logobase + 'FnCnW1vmvm8gjAwMGXe2Q8qtN1C3zy.png', u'Hustler HD': logobase + 'LBz8ia8AASewVuLjMs5v4MDiVYfsJO.png', u'Hustler TV': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', - u'iConcerts HD': logobase + 'fLNDK6Nxz7xMv61nOsZvED749DlOtz.png', u'ICTV': logobase + 'YuNtYxhj9vqgTU9kVuz9imhqviY4PZ.png', - u'ID Fashion HD': logobase + 'v5Ahro1G6KQVugydvnyZbHjhM6BxhR.png', + u'ID Xtra': logobase + 'fq3ovkZeu4R4YVYt1VMZN1NU7mMrH9.png', u'Idman TV': logobase + 'UhhUZdtM7FjDFbmoleWalj7tp6nj5o.png', - u'ILand HD': logobase + '3REFaj227EmEOfEbNCXz7eWzle6z1v.png', - u'Info TV LT': logobase + 'Sv42hVuB4nb2y04ggrhadg9FYZI3Kw.png', u'InterAz': logobase + 'rdntbmpYEYtyRbW3nGa1FUNZPXF53O.png', - u'Inva Media TV': logobase + 'mGWpCUkls40liFDUVrxhaIAFwYXZSt.png', - u'Investigation Discovery Europe': logobase + 'QvF9d3DYyndsYsyfjC8aWeSzy6hpns.png', - u'IQ HD': logobase + 'tkCaOFZ7Xf1TmIzhazLXZlcWfJa8Od.png', u'Jahonnamo': logobase + 'OqNuJdvRTdBh5NeydnpmCfMnTJs9XZ.png', + u'Jasmin TV': logobase + 'MBRUFcRx5wtHLZmgahvcGdXB2ERdst.png', u'JimJam': logobase + 'BPDFCK5SQF3mXu5MsDNSdtvz4Gjawo.png', - u'Kentron TV International': logobase + 'Zup40Uv14kJO1x3LUTd5OlFzE0uTPj.png', + u'Kino Space TV': logobase + '28A3JkU9kVHQWimTbBinKpqYyjZHcA.png', + u'KroneHIT TV HD': logobase + 'dq6m0UE9ffgAbXNt49qDlIAr7T8lud.png', u'Lale': logobase + '99AZ5VwFy9yZrgsdZ6kIxcoYqwSyUH.png', u'Lider TV': logobase + 'LByLhdeQ30Ln5pSZRYSpoLpXQS5Ytr.png', u'Life78': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', u'Life78 HD': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', u'LifeNews': logobase + 'K8dBCUNz1BSwbgUYYU04i2qJpQKLMc.png', u'LifeNews HD': logobase + 'Mvurp6cp7Sq2fV3tnFBwPtJy7Ifm1i.png', - u'Liuks': logobase + 'vUvAku3X6MAJdJWbLC2xbqXnyHEDpR.png', + u'LNK': logobase + 'svzmHjJUDAOijKSBd3HL9XTvBOBPij.png', + u'LRT': logobase + 'sgH7Pdrd27V3cvlvg8OFOzsL2gLbcF.png', u'Maxxi-TV': logobase + 'uqyYr07PPk2OuEdnNrbkMHVlGZCJp5.png', + u'MCM Top': logobase + 'b4SLIxXFbKUErZxBXfGoo6eQN5Mu38.png', u'Mezzo': logobase + 'GUzJ4cyK50ZahOSIx6tTbwGLJiUnSA.png', u'Mezzo Live HD': logobase + 'lGOtRiUoGyJUMCtAtUwevBerpQORD4.png', u'MGM HD': logobase + 'o4K0lgc2D1GkuFwMC3Cdg1uK6fMrG4.png', u'Mi Lady': logobase + 'Fd556PD2ffD6zdVUiX0dvVfBJavBNd.png', - u'Motors TV HD': logobase + '5t2PirCczEeIqzonCtgbmgpbyOZO5q.png', - u'Movistar F1': logobase + 'no1c6DhrTCZVqnq9N4Z4SZ2dKx3DyU.png', - u'Movistar MotoGP': logobase + 'nvj6jOmluzhKXuUoXWePwrccFE3DO2.png', + u'Motorsport': logobase + '7fAmhbXY4MwyRxACHpWPIaNqNiW6Y8.png', u'MTV AM HD': logobase + '4jFcW4ycRkxdcHqHaVM8J2oIxV7re8.png', u'MTV Dance': logobase + 'ZXKNRw6Ai8u4lY0wxeycQwj2dIkm66.png', u'MTV Hits': logobase + 'iLJhuLh9kFLQkG4ERvLGSjSgMfNiM4.png', @@ -206,48 +186,42 @@ u'Music Box RU': logobase + 'zaHCW7nPCyGRnqHDkenCIXo7d6vR7v.png', u'Music Box TV': logobase + 'fvt4pris0lwnVhSyUrh8QlyzWBhbgz.png', u'Music Box UA': logobase + '8T7Rnct8q2VHhRm0BcGFxCjxuYEArc.png', - u'Music Box UK': logobase + 'KUbeGDe2HthXSDa8OqMhia0nhTSL61.png', - u'Music InTV': logobase + 'QTJnJokXeUpNksMV5cp0TiE9lyUesQ.png', u'Mute TV 3D': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', u'Mute TV HD': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', - u'MyZen TV HD': logobase + 'Nm18g9xkyhx73F2vxLCPYVRwX6k0H6.png', + u'N24 HD (Germany)': logobase + 'j0NiKCcpOnPBobWAE0CVBLsBhSLgCt.png', u'Nat Geo Wild': logobase + 'ciHIDUuHEnkEuPghbcqkDQx4vadle3.png', u'Nat Geo Wild HD': logobase + 'YYa1wyNA9prFK1APZ2ZSHGirPpm8kY.png', u'National Geographic': logobase + 'i6STSw6Hg1wWP18yBAOyKoKpSMeKLu.png', u'National Geographic HD': logobase + 'hK1waimMq9eAp0ugM19moSoQvUeve5.png', u'News One': logobase + 'SnaQqBa1YMS2b8b8EGdZSMCConLbDS.png', - u'NFL Network HD': logobase + 'Mr01K0LgGHKs3Zd1fnvMTuBSFyvu40.png', u'NHK World TV': logobase + 'JJ8Sh9c7zA3PaXlK1ZaNjy3GgWQFh2.png', u'Nick Jr.': logobase + 'D87kJ3fIWIm5wKi5qxm24nbuPQv0U8.png', u'Nickelodeon': logobase + 'j66xpaZbfiYIgQxv76QAPckPVjmLNs.png', u'Nickelodeon HD': logobase + 'CFYx7Bd1aDSgkqjMLLQjFE6xe3u1E0.png', u'Nova Sport': logobase + '8npwMe6SAEgj5nMBCoVPCemX9fKOvI.png', + u'nSport+ (Poland)': logobase + 'SVdxnEDeE280C8YVqsEIsJermTq1Ks.png', u'Nuart TV': logobase + 'aqMIuUixqLQYmJITPnOGtFkRPuTqKa.png', u'O-la-la': logobase + '7ddvb8Tivq7yuEgffrKildHi2BQcCE.png', u'O-TV': logobase + 'UlIt4rE3FZs7IQmPN3tsGS6fqY5F1D.png', u'Ocean-TV': logobase + 'cvBAngU16nJU1bEzxAEcMPiPvf7fVT.png', u'Outdoor Channel': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', - u'Outdoor Channel HD': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', u'Paramount Channel': logobase + 'Iyzjhb5BBRKfvEZdBwNJ785jzroODU.png', - u'Paramount Channel HD': logobase + 'Iyzjhb5BBRKfvEZdBwNJ785jzroODU.png', u'Paramount Comedy': logobase + '5EtvAWXB7VK1Yw82yvO28sY28dU4ZC.png', - u'Paramount Comedy HD': logobase + 'AvH8i4NV780Q7PcHi5KkxnCDU0Y0yl.png', u'PassionXXX': logobase + 'U2oO3j1eda31nrTwHqaQ8psrLfH5CD.png', - u'Pingviniukas LT': logobase + '8X45gGlJq2Vx2jUts3EhYESzpwjmPo.png', u'Pink O TV': logobase + 'aI99kH6bHY5Qt2ph4W1Nh0wxdzd1p8.png', u'Playboy TV': logobase + 'lIWBxmt5GDl9tg4KsQNtA0CuZWdOHH.png', - u'Poker Central HD': logobase + 'XfvAn9FaPBiRrhXBluK2kUPqxSftAo.png', u'Polonia 1': logobase + '3fzUyCBgUoJ6zqc92KSXh9g9utJEuO.png', - u'Polsat Sport Extra HD': logobase + 'AdIBJziuh63ffasA6KeDMAKV9DPBqL.png', - u'Polsat Sport HD': logobase + 'zAC1zjpeHrGa3vb5nPLvvNJ8SooQes.png', - u'Polsat Sport News': logobase + 'WhGmGGlQXEvepTfPBd3u1to4ekbtDN.png', - u'Premier Sports': logobase + 'dIKsh48t49lFlDcKPhHKsvYsKaOfNS.png', u'Pro Все': logobase + 'OavKhOpBMn27qPDKJXXm5rgATgovgn.png', u'QTV': logobase + 'zmh1xStjBZGJ6U5g3xLoemD2oT3w3h.png', u'R1': logobase + 'OducDuiwKukJTHXoQo49vuh62PvXK2.png', u'Real Madrid TV': logobase + 'KfoLG7goywcRhMMCc3sO8IVwhuATLp.png', + u'Redlight HD': logobase + '5tFZcJtKAZRbXtKGDuLe8FZ3lK9LI5.png', + u'Renome (Одесса)': logobase + 'uXenrBCtSwAsv31RtOAXdn3hXm3i5N.png', u'Retro Music TV': logobase + 'zVS9G1oine56udlAK30gNut16iJ9Ft.png', u'RMC TV': logobase + '3CiAgachWg7ohgoU1Gilcm73hXhT41.png', + u'RTG HD': logobase + 'g4MDyw0yqXWkIar8eH0cgCz4xhiKON.png', + u'RTG TV': logobase + 'IeaOjwR6Q9eJjGZr0LYk2tpchM3ITZ.png', + u'RTL': logobase + 'Ekttfk03O2aNYv6OoYlVFEAykjDzom.png', u'RTVi': logobase + 'QBnba0xrWtpPWLL4yKDRixaCRQAmaP.png', u'RU TV': logobase + '161.png', u'Rusong TV': logobase + '186WxZMn3PGQyMlWsItM9JkSS4Tt29.png', @@ -257,31 +231,23 @@ u'Russia Today Doc HD': logobase + 'b8QePfFi6zsCDS7hfTeFWES5UN4SAk.png', u'Russia Today Espanol': logobase + 'zkPwjfFbktNO8EYDaHlYhwAeT88dmY.png', u'Russia Today HD': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', - u'Russian Travel Guide': logobase + 'IeaOjwR6Q9eJjGZr0LYk2tpchM3ITZ.png', - u'Russian Travel Guide HD': logobase + 'g4MDyw0yqXWkIar8eH0cgCz4xhiKON.png', u'Satisfaction HD': logobase + 'isdNgbfGENuaDPSMzsz8WMjBzc1rah.png', u'SCT': logobase + 'bDRN0guXHaNHruxtyFkpKYz8J09Xj3.png', u'SET': logobase + 'tKzOKIcOQYrFl1VL8B0QqERFXCAYfU.png', u'SET HD': logobase + 'sX1unYoKj8JR7m8lbtkmPCClRrjAZ9.png', - u'Setanta Sports Eurasia HD': logobase + '1wgHdJP76TCItF14FxDwBtak8tmxRv.png', u'Sextosenso': logobase + 'HXMvFMLO9weHUcolVmmMKpz98T0K4K.png', u'Shant TV': logobase + 'IgJpH1JzyjI55Ki2G2Ybz6jzOkwG0T.png', u'Shop 24': logobase + 'bCuxLyvoTk8l5cBRSyKFXjWjYucvlu.png', u'Shopping Live': logobase + 'HVwxC489SYFr8Ttqs1he9RZjEmJjPn.png', - u'Sky Sports 1': logobase + 'pzVzAnJJ90768a73nJXWFAni9yYPT3.png', - u'Sky Sports 2': logobase + 'iCLUex5RU5u1AbCkyiDJVfZgX1Y5BP.png', - u'Sky Sports 3': logobase + 'UAUhQNqNqyEgXdUoeffjssUZ262Lsx.png', - u'Sky Sports 4': logobase + 'qqJzEqYys67VAtSt59keaQaJdM4LUg.png', + u'Sky Sport 2 HD DE': logobase + '4dPVkJ96LwrNCbFCii8dJwJC1wEAzJ.png', + u'Sky Sports 1 HD': logobase + 'U4ngWpMf0MTbeOpX4Tgxelz0tPCBs8.png', + u'Sky Sports 2 HD': logobase + 'B40l6w5yVEpOpZ31io1VzkubFnygLT.png', + u'Sky Sports 3 HD': logobase + '5Ft6qyJQXmV1MogsjpO79oKKruQj64.png', u'Sky Sports F1': logobase + 'J9T7KUE84YobHuNThXN6Hl9UG21tD9.png', u'Sony Sci-Fi': logobase + 'zLWEgf9BbxBr1TR2Qj2NxVQBuPecEP.png', u'Sony Turbo': logobase + 'CaPjVaQrpyN138TarQ7CYBqBOz0ZF7.png', u'Sport 1 Cz': logobase + 'kCLlfkFz3Ba3BL9Jc9ZPgUKXh2piyv.png', - u'Sport 1 LT': logobase + 'nRFc87aOV1vRnjEqmQZUneZe4HiCqn.png', u'Sport 2 Cz': logobase + 'YLmEjnczWQGJcZC0SxRcH4ifPcwYlx.png', - u'Sport Klub 1 HD': logobase + 'cLQ3uuWhQqxCQk5RUDwA9x7bLUHBwn.png', - u'Sport TV 2': logobase + 'u6T8L5PPYKHCbBATjdzjLpTC8zzCdV.png', - u'Sport TV 3': logobase + 'dYTM6Oqhaqw18FI6uYPS5yhjCmc1nZ.png', - u'Sporta Centrs TV': logobase + 'FJky9xgBrm265yuoNH3K2OZTINMLPg.png', u'STV': logobase + 'DbKEKL5gUOFHiruYRjY2H9gTLOV5mu.png', u'Super Tennis HD': logobase + 'mjQW91VJdjIEhADvOO2s6OiKNeUdUK.png', u'Teletravel HD': logobase + '4ZlASq3oDpOjXfhwluOzY74sy9elaE.png', @@ -304,22 +270,22 @@ u'TV Safina': logobase + 'mJUmNhJbQqcr2NPppAryEJqDPBJGV0.png', u'TV Sale': logobase + 'hs0YdiUTlpRtb3wTiP4cXboX0H9oTN.png', u'TV XXI (TV21)': logobase + 'TKchoTWZFRMmGDBok08zoEFJ8mJJCe.png', + u'TV1 LT': logobase + 'CXD4Zq54oHMsByuJbWtdIqzD24dVVo.png', u'TV1000 Comedy HD': logobase + 'ygGiR2hkQLySH6khdo8GV9CyMJ8dXi.png', u'TV1000 Megahit HD': logobase + 'lVPY7WCjn1WM6NL6tfLFy8iGA4yk3Z.png', u'TV1000 Premium HD': logobase + 'raoDrpin8VKmi522LZWzSF0fLRO04m.png', - u'TV2 Sport HD': logobase + 'iL3TM972YPxOxajyfbuNcKGPFrVvTg.png', + u'TV3 LT': logobase + '0OYDSD4Gc81rVyDm8YbqvY5gY0dNzZ.png', u'TV5 Monde Europe': logobase + 'ko7rbRBnyK1iINkLOA2adRvgVOEgUK.png', - u'TV6 LT': logobase + 'SKskx67yBUvbTdMIIZjH1Z4EcB8nYX.png', - u'TV8 LT': logobase + 'X4DIGhjllSDaaXoZ98BrpkFQ2hHfB6.png', + u'TV6': logobase + 'l1UpPzlU6nnHGIAdtNGTgns5V2l6jV.png', + u'TVP Sport': logobase + 'AkCqdaRAZAZY0PKGoKZkhDlBCF9RR6.png', u'TVT 1': logobase + 'CKKdhDfmno9O52tMfWptiAQT0IBWV8.png', u'UA:Перший (Украина)': logobase + 'iN5vheceBnPmO18uXHGgKwq7q2PHtF.png', u'UBR': logobase + 'F6EzmjkOBVB0gmn1kQX6itv5VvFml5.png', - u'Ukraine Today': logobase + '3AVq6O577A7uw9uZ7fxIvpvE3CxdtW.png', u'VH1': logobase + '58.png', u'VH1 Classic': logobase + 'FhxUFQ2Bsfom4vb8Ce41gFObAbh1Vh.png', u'Viasat Explore': logobase + 'uCqpsdKP0ialUUYxUk2fXshYdYfxzW.png', u'Viasat Fotboll': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', - u'Viasat Golf': logobase + 'IGpQl5iTxaDEPyKffdDEpU0EU0SPiO.png', + u'Viasat Fotboll HD': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', u'Viasat History': logobase + 'MWGbB8wJp5Gm4vbPHl0ktohDDjMKdr.png', u'Viasat Hockey': logobase + 'CuAbCRGdf3Z1FGFiwErTbHZ3lAMJzr.png', u'Viasat Motor': logobase + 'RuYtGxEpqJ5DG7WxGCMWNDXosRdh59.png', @@ -329,20 +295,18 @@ u'Viasat Sport Baltic': logobase + 'ZIITckvF1w5u1MlubmhoG45HxPgcZZ.png', u'Viasat Sport HD': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat Sport Sverige': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', - u'Viasat TV3 Sport 1': logobase + 'LUsZ9yjy6izQJHd2z2Hf7uBZ4UyUcM.png', - u'Viasat TV3 Sport 2 DN': logobase + 'bIcffZkSHB46rCxbvKYcIl1OSfrcDf.png', - u'World Business Channel HD': logobase + 'fMdJvc8moH4HGH5OYqsZwskCowQYuE.png', + u'Virgin Radio TV': logobase + '6DgkEMl3HtkpKQbzVovPdSYhy9f3ne.png', + u'VIVA DE': logobase + 'HagNMshKtJ7zKnk9fdmBLhITjoWdrJ.png', + u'WBC HD': logobase + 'fMdJvc8moH4HGH5OYqsZwskCowQYuE.png', u'World Fashion': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', u'World Fashion HD': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', u'XXL': logobase + '6nJtj85PlL0MxB8RDkM3toyGND3Anc.png', u'ZDF': logobase + '5SH5FeZiITw27CPxscjksZp272u7He.png', - u'Zee TV': logobase + '1HooaeEhMSvpKmWv6nneZxnTmG5r6Q.png', + u'Zoom': logobase + 'SyisYhg411o7z9kXci4vfpLq4KBZZ4.png', u'Авто 24': logobase + 'GZwKgvkC0vfYquFAn1dKhppNxdDit9.png', - u'Авто 24 HD': logobase + 'GZwKgvkC0vfYquFAn1dKhppNxdDit9.png', u'Авто плюс': logobase + 'WkRxjy6fJEBJ5NZiaGn2j05eqfFfQq.png', - u'Беларусь 1': logobase + 'v5gOoahV1MZrNsZz6pCp335pW8uYHf.png', u'Беларусь 24': logobase + 'GxA1KJP5YwpWc38BoPEmLwQH6uDeEz.png', - u'Беларусь 5': logobase + 'aMU4HXJN11Bo9WissbPW4rhe06vAql.png', + u'Беларусь 5': logobase + '2bzJLE9Sbk9BqUBlsVrtqpsz8h0rB6.png', u'Белсат ТВ': logobase + '9VYuUQxx1ss7ieu2upENtlibyamBP0.png', u'Бигуди': logobase + 'JvcMdB5e6KVBpbXT12ulzmDqenheRx.png', u'Бобер': logobase + '2Edln8vEbg7UUSVUo7lIJPR780OWAR.png', @@ -351,35 +315,31 @@ u'Вопросы и ответы': logobase + 'xbV8M35FkvpieQ3TUEL8fhwU8MzjmQ.png', u'Время': logobase + 'F44yKDJQLsX0llpZ2wupg8V5vHx5fF.png', u'ВТВ': logobase + 'svsUD6TinXyv3B1q5sZf3fI9ebmpaF.png', + u'Горизонт': logobase + 'f6wqkzO4WZW7D5Z9xIB4VkMRGmgXUU.png', u'Громадське ТБ HD': logobase + 'Ovkd9TiVv3nLcKPwQS2wkJ85KyYCMQ.png', u'Детский': logobase + 'jk8kody2p38CKdj5KGXWMwRLjgFIlG.png', u'Детский мир': logobase + '00Vf3rPABNnbNQ6Rv0dnfcg3JsJelA.png', - u'Джус ТВ': logobase + 'qVNFoyUAOJSDvN9tHhf9j2AP7x4VkV.png', u'Дождь': logobase + '381.png', - u'Дождь HD': logobase + '381.png', u'Дом кино': logobase + 'jlC78Fy13KWjQUN6l3FtbsRLZDvc0x.png', - u'Дом кино International': logobase + '69MqZE2YHJNewQkqRbJea33WuRkKgo.png', u'Дом кино Премиум': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', u'Дом кино Премиум HD': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', u'Домашние животные': logobase + 'HiWAmn5RvUKNJnSW2Jhxjs6maoNFV7.png', u'Домашний': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', - u'Домашний (+2)': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', - u'Домашний (+4)': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', u'Донбас': logobase + 'vsj1IA3Z8QVL3AzzuN4EshgT4LUmRr.png', u'Драйв ТВ': logobase + 'pmmgMKcRbxeYkjVUVr4IAWM0UuZHO4.png', u'Еврокино': logobase + '34mszCG0j0Vf6kFcMrLPnFEA8UPdu6.png', u'Еда HD': logobase + 'ojUD1jhpv7HBOLmubpEBOsANkpYNtk.png', + u'Еда ТВ': logobase + 'TWdAdMXfMSylb2mQ4efFnOAYosymNC.png', + u'Ескулап TV': logobase + 'onT5lg0NOm9BSORR7KNsO1oRA9pY1S.png', u'Еспресо ТВ': logobase + 'lOwm890F5URuR5Ej7IacerzECPIDt4.png', u'Живая Планета': logobase + 'xgKSMwqBdEyXnbVgb8LtNXSMiaPcOx.png', u'Живи': logobase + 'cOluSjslxxs3JZtSVO8c15xh7h8SDU.png', u'Загородная жизнь': logobase + 'cGGo8HRkVhy66UXKXZ4tH5HyUaaxJA.png', - u'Загородный': logobase + 'RX345W0BBqJbR3XdROHMi4dnbwqwlt.png', u'Звезда': logobase + '0HLRrFHt2QIkbJpLc1fy0RVe7hqCEC.png', - u'Звезда (+3)': logobase + 'IVxzhUbLe9DUhOtnjpDYpITMrBdybS.png', - u'Здоровое ТВ': logobase + '2LgJcyMnjJpMAhUqX3rdQ4ChOmbuTo.png', u'Зоо ТВ': logobase + 'RtAhntWPlKQs6CIYAb72piNF9EsN3E.png', u'Зоопарк': logobase + '1Ugpb5T1THFcFpn19Mnua21KxHkjct.png', u'Иллюзион+': logobase + '8LToTNvWRBHvb5IKoteKm8EwAGw8mv.png', + u'Индиго': logobase + 'dFIp5shmC5DbfWIDVaFh7coAofmLON.png', u'Индия': logobase + 'XVWyHt5bFFcZNzmysBSjuVdGBGl45D.png', u'Интер': logobase + '3SP67FapzyZqMVZTPiJIcN09KRkTeu.png', u'Интер+': logobase + 'QEdaDBbqr13CCfwKQAP77UZYPQIPn0.png', @@ -388,21 +348,28 @@ u'История': logobase + 'PNRaeOUFzOPFtrclFBBRTckj6Lvo0u.png', u'К1': logobase + 'mk2mYb28HFIxkFIiMNQWmKUdn1Y8hD.png', u'К2': logobase + 'IjG76jf8k8HTNLooNpUiEXtkPfA2rG.png', + u'Калейдоскоп ТВ': logobase + 'YJMqmzlZ87QeXYm9XpjH0XzpjljNcU.png', u'Карапуз ТВ HD': logobase + 'ieXzw5xeahW3OoO66m2LX8GwLCYyYl.png', u'Карусель': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'Карусель International': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'Киевская Русь': logobase + 'C1AZimW2NnNA17H1uJLxxePUMTPQZ7.png', u'Кино ТВ': logobase + 'KkITMDICqC1erWdSqyOqoccqde2wHC.png', + u'Кино ТВ HD': logobase + 'glnk0UNz0DwfqxgJh4A5Emo0tKrQAZ.png', u'Кинопоказ': logobase + 'v0JEbxExcFI8dVEzCkpZUoktgiS9t7.png', u'Кинопоказ 1 HD': logobase + 'pNA1vR3sPoovYNKzO4TU6NQcSXNqjk.png', u'Кинопоказ 2 HD': logobase + 'Yf2XKrtorOF2wSZ23Q9NHhL2MfOcqi.png', u'КиноПремиум HD': logobase + 'p580CRZ8bBS6dw3plMWhhxXSzQ59uS.png', u'Комедия ТВ': logobase + 'L2MEpT2YePoDvmKRjYy6yyt5ssH1m4.png', + u'Конный Мир': logobase + 'd8gVne1Em14rIfM6pSsiuufXZeYQqi.png', + u'Кот ТВ': logobase + 'Rtdtc632nbBQ2TsymgXhkOt7nhytjW.png', u'Красная линия': logobase + 'I43S6jd5noclar0LlPJnyY8adonmUV.png', + u'Круг': logobase + 'fbzJYZiRscBiEvaWEFmzvYzEHdaNN7.png', + u'КТК': logobase + 'BhI4KURCZ3fAgCQcwQWn7vRCJQblH3.png', u'Кто есть кто': logobase + 'MwNkO3fXd6KefRdiGlOdOQ5q0Zu7kS.png', u'Кубань 24': logobase + 'CAAqiN96tQzFDdtz3vjrrgeIjAKqNq.png', u'Кубань 24 Орбита': logobase + 'FauvJxsKmI5a1fR62uSH9hJfHs5TCr.png', u'Культура Украина': logobase + 'pyKdve4YhoChQFGSha8J0FBWBf302a.png', + u'Купи Дом ТВ HD': logobase + '7CyfedwVltdaMcZXD3Xx7wa50mZJom.png', u'Кухня ТВ': logobase + 'G0WbVMphlP9oJ6KvHRfx0xDfhrF9Re.png', u'КХЛ HD': logobase + 'kRN7BwVtcdaXrU4Mdg24qhFAxjx9oZ.png', u'КХЛ ТВ': logobase + '216.png', @@ -432,6 +399,7 @@ u'Мир (+3)': logobase + 'QxOYkz6f80IdhmC4RSHI1cMd32CqYZ.png', u'Мир 24': logobase + 'auv6717gJOWi0A2VoeDQaCsx9G1NOj.png', u'Мир HD': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', + u'Мир сериала': logobase + 'n3zRAlCCBLl5WOeunWGpuMmfdvSJcW.png', u'Многосерийное ТВ': logobase + '4TMYdVpZYXafyIumuB5d7PrjFnslyT.png', u'Морской': logobase + 'uzlb3awoyNqvIcf6i35hTVqf7gvuqz.png', u'Москва 24': logobase + 'dZcmoqRoZLhCBh8BE4RnbQivuDY6hH.png', @@ -443,6 +411,7 @@ u'Музыка Первого': logobase + 'fD2Hnsq5BPMGvobLDMPZP049yNhBYt.png', u'Мульт': logobase + 'ZVzHvGF8mZ6RTsSh6aWsPbF1FBLjyp.png', u'Мультимания': logobase + '132.png', + u'Надежда': logobase + 'fvCkzRJPsTx4HONWPv3vPMjQNloPBT.png', u'Нано ТВ': logobase + 'QuURIfJUmXegxsHMYqMivVwxizbfKd.png', u'Наука 2.0': logobase + 'ypWbqYqKApM8cnDK1FibvQgpmgEay9.png', u'Наше HD': logobase + 'NyV9o2C4xIMFxxYUQsS1qsdAtkqz0k.png', @@ -452,12 +421,12 @@ u'НЛО ТВ': logobase + '2VGhYruaQo19G1NLGoOiTrwmPxef7d.png', u'Новороссия ТВ': logobase + 'zUchDq13UVJRmlwAl3feV8cgKHYSyE.png', u'Новый канал': logobase + 'k7YdHhVpFZPIkBMXS2P2O2TkZSPf0y.png', + u'Новый Христианский': logobase + 'gf6hTOcGXasvr47vTFRYZGV11xkDr5.png', u'Ностальгия': logobase + 'tIfiXoDaXoZevuGu9pZJSvX8unv1xl.png', u'Ночной клуб': logobase + 'nXifSdkxHJVKI4SKtgtBQmCSHXtgOt.png', u'НСТ': logobase + 'fKYzdlWRz68qd9mRZnWuxMY73EyaSz.png', u'НТВ': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', u'НТВ (+2)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', - u'НТВ (+3)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', u'НТВ HD': logobase + 'zdJ3ye6d3UWl5a56zm6LjqYH6ziSOs.png', u'НТВ Мир': logobase + 'ykpU49Gl1akVkgiVSsCm5B4TjXvQ64.png', u'НТВ+ Кино плюс': logobase + 'cnz8ZMypP2HV6phwv3rkSVQ7CgJExi.png', @@ -467,6 +436,7 @@ u'НТВ+ Наше новое кино': logobase + 'RcsEz5rIV3hQ6cwn6hqiwNAEOh1zNp.png', u'НТВ+ Премьера': logobase + 'lDiI54Y3LjIAOg5VV0adicP3OJrdgo.png', u'НТН (Украина)': logobase + 'LpQE1Odb1EoH5dJ90gWjItVyEYBXsw.png', + u'ОНТ (Одесса)': logobase + 'P3tnfUDZ7f025rw6X7rFNT4aYmwMlJ.png', u'ОНТ Беларусь': logobase + 'a84If8XdqSFa6nHpegdujt52vAGNJW.png', u'Оплот 2': logobase + 'EqwpuUgrI6Wl6JVDK2fLtXkNaqOXeU.png', u'Оплот ТВ': logobase + 'gvofGxTug45qSt1vsX0BPzQxGTrwTr.png', @@ -475,16 +445,13 @@ u'ОТР': logobase + 'CqxKorK72v3ULbWkB3ZOhdte0duYZa.png', u'Охота и рыбалка': logobase + '5l2P20J6ebTh0ptOr27Hh704niP3nU.png', u'Охотник и рыболов': logobase + 'Ws2ddPI0b5Ie7PymoPUsboVlz9lYMS.png', - u'Охотник и рыболов HD': logobase + 'O0wexOqiQXKMwr2coDMKWvJEb4zLQ1.png', u'Парк развлечений': logobase + 'beyfqyeacrFG0PrOeKUQhzQ4bV6Q5d.png', u'Первый автомобильный (Украина)': logobase + 'oZTXrmNOxeJIVSbnuxqbiuAL3voXYa.png', - u'Первый городской (Киров)': logobase + 'sxUNuJVQpUjRMmASa5TvwlGykSBAkY.png', u'Первый городской Одесса': logobase + 'vBOI3YTA4FDLD0c7BHHjq476p9GMCZ.png', u'Первый деловой': logobase + 'a1Qf3MpxC9FPD68Tj8vtUTNK8P25xr.png', u'Первый канал': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (+2)': logobase + 'Lo2zy8h0msIieULsbHOjV1oSInHogr.png', u'Первый канал (+4)': logobase + 'nHJycH0CkOhPeZ9DmB47iSMWP5HyWz.png', - u'Первый канал (+6)': logobase + 'xEhi4YWxLlIcHq33Y44NrYvyHRArwa.png', u'Первый канал (Евразия)': logobase + 'oPB8TcSFAuJtk4hBWgAqC9yNjMpfsi.png', u'Первый канал (Европа)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (СНГ)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', @@ -493,32 +460,30 @@ u'Первый музыкальный HD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', u'Первый музыкальный UHD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', u'Первый музыкальный канал': logobase + 'gYpYhzD3akuKSFpRmkh2p36pXnqHoW.png', + u'Первый музыкальный Россия': logobase + 'MkX2WG1zhZ2KcYFdL0xWH1T4xkO7UW.png', u'Первый музыкальный Россия HD': logobase + 'h7EDhdGypKmtfEP98O052SLlXUCcXt.png', u'Первый образовательный': logobase + '1kXxtStMuodaPU09H3rla3ry3QA2Wr.png', + u'Первый Республиканский': logobase + 'Ubch3Ma8hsdSd8u6spOxVK5bZYsnDs.png', u'ПИК': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', u'ПИК HD': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', u'Пиксель ТВ': logobase + 'BdCXB7wPZMNvlWzB5xEFzmsYUXcfXW.png', u'Планета': logobase + 'o7wnxC58C4KfCnX0hh0il0LTRlPfSg.png', + u'Планета HD': logobase + '1QjirpCLi3q9qPu1CTvEvCB0BfINeo.png', u'ПлюсПлюс': logobase + '6gVIy7RMokFO61iVawgwbthe5mhgqm.png', u'Просвещение': logobase + 'Fpx3Vqqk2VNcXl4YjsfO53XscWadvF.png', u'Психология 21': logobase + 'AyLAdiqcKu5X8ykdLf2bO9HsxMlJdO.png', u'Пятница': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', u'Пятница (+2)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', - u'Пятница (+7)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', u'Пятый канал': logobase + 'nIUDYY41OO4Xo0ntGpGv2rfpOR5ngt.png', u'Рада Украина': logobase + 'hBFJBYNiqZUom0ooVtNEJKliZwfioO.png', u'Радость моя': logobase + 'VRylZFYgFq7AL0FWcbf5JVOX3desn3.png', u'РБК': logobase + 'JUMDXZxxB3UiVpMpU8t0aCpbVzxTmP.png', u'РЕН ТВ': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', - u'РЕН ТВ (+3)': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', - u'РЕН ТВ (+4)': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', - u'Ретро ТВ': logobase + 'axrNIB7372SHIRwqT0jBbfyvjSoZ7I.png', + u'РЕН ТВ (+2)': logobase + 'WeHcyu6MG9GGpCmToRQnoL97iesKk9.png', u'РЖД ТВ': logobase + 'qa6177IUBWR0hYt1HKXxlMrF5MStOb.png', u'РИА Новости': logobase + 'DixgG6tVZzcVHO2LPQEx3QrtfoVah3.png', u'Россия 1': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', u'Россия 1 (+2)': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', - u'Россия 1 (+4)': logobase + 'ilXh9tpOhLtf4YREIyUACXh8TCxQVT.png', - u'Россия 1 (+6)': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', u'Россия 24': logobase + 'LWfGV6eICPYL7psaBfw2dOgGrOtHFS.png', u'Россия HD': logobase + 'ghvqmVpPWqn9x6POAm9UJBvXFzTrqN.png', u'Россия К': logobase + 'W9pWrec1BOJTmj8okrFeyM44wcpyd4.png', @@ -531,11 +496,12 @@ u'Русский иллюзион': logobase + 'E9Imfr8aHN5midPVpNhJ3fo49FHbQE.png', u'Русский роман': logobase + '2smriIFxtj7Ojh4jyZq0K1XrT98XjS.png', u'Русский экстрим': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', + u'Русский экстрим HD': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Рыжий': logobase + 'wfBSy60qHaPSKPpTfrNv9Q167iHIPu.png', u'Санкт-Петербург': logobase + 'sb81YtPOvlHidztMnC5tZPSKkb1uMI.png', u'Сарафан ТВ': logobase + 'LsYzwEOUspoxkY2hrTSy9zKqvpWlY8.png', u'Семейное HD': logobase + 'ORgdUno4IvNQYiShiEY4srF16JguLs.png', - u'СК1 Житомир HD': logobase + 'oGKMU5pGcZElcU6sLzuDZ4ZEoSislJ.png', + u'Сказка HD': logobase + 'dxhsesilX4JjwoCc8Qab4SYe8fxm75.png', u'Совершенно секретно': logobase + 'BZJQEpa6Y4KL9tQjPHAIxbodw0KAyN.png', u'Сонце': logobase + 'TJXJVeoBFRMFrUgzpPW4dunJL6XSzn.png', u'Союз': logobase + 'YpsuBorUwulPHW3nI8O6nKETnEVB83.png', @@ -547,20 +513,22 @@ u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', u'СТС (+2)': logobase + 'FWNPupxL8N0STWKYEryyiV5sDFN2tS.png', - u'СТС (+3)': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', u'СТС Love': logobase + 'iciJHbEmJ1hHXAMhzC9cRWhmh9gH0L.png', + u'ТА-Одесса': logobase + 'xnCANqGy0KUMtzCttmO9jyZUsXlEEI.png', u'ТБН': logobase + 'r9O7HmwQbFR4oKMH9yKAogE8xBzwz4.png', u'ТВ 3': logobase + '427.png', u'ТВ 3 (+3)': logobase + '427.png', - u'ТВ 3 (+3)': logobase + 'YzjZZoc01zFHW2bm6CuP2jkHo0byV3.png', u'Тверской проспект': logobase + 'k6RPWpDIwfsOZ5qkqHF5ZdOf5GqfmH.png', u'ТВЦ': logobase + 'QEpQTskZ9hcfI0rgD8osHVYSv58pde.png', + u'ТВЦ (+4)': logobase + 'dR7hMBOIq0MDGMkydFuksHGLNIWz7U.png', u'ТДК': logobase + 'eSrHE6Gws4U6JxhFXA3mQ4iDVc0SwS.png', + u'Театр': logobase + '8qawrcOtzHZTAa3kI0rcDfVFEZoI5u.png', u'Теледом HD': logobase + 'XviuCfRo0T4WFTOhFaC978AwZ1a3Ge.png', u'Телекафе': logobase + 'fYRFV5oY197jXcyModfWVs0AlrCOIs.png', u'Телепутешествия': logobase + 'fz4bqwLySJAQkUN7l2EPKNqyvilfRD.png', u'ТЕТ': logobase + 'jp0YxRwXOyMWgVfDAyQaXNwle90sV3.png', u'Техно 24': logobase + 'JbUGHLuuZa3WQbjtbzUo0cDZkGnLRK.png', + u'Тиса-1': logobase + 'pPRRy0SjPl3JKXdcZrQrdXQz3NTKOH.png', u'ТНВ-Планета': logobase + 'vBMv8AtIpDhBGQLjPoxkko3baWMFac.png', u'ТНВ-Татарстан': logobase + 'kMdYm3qFLgK52EV0ymvRBB43peSrj9.png', u'ТНТ': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', @@ -568,10 +536,12 @@ u'ТНТ (+4)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', u'ТНТ (+7)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', u'ТНТ International (Европа)': logobase + 'd4loXdWqOPiwF7thzyBXX8JSspfVjU.png', + u'ТНТ-Music': logobase + '6Go23tY9hpakfrvTEUH7Z7o5Y9hpOG.png', u'ТНТ4': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', u'ТНТ4 (+2)': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', + u'ТНТ4 (+3)': logobase + 'dg9BEAtJr4IthpXjxV8NGAiu95NjXl.png', u'Тонус ТВ': logobase + 'bE8WfReOerYTIbqPOo6VD2ajrFdOBT.png', - u'Точка ТВ': logobase + 'bDZ1EY6wCnzKYyh7LjonM4fdy9ExvD.png', + u'Точка ТВ': logobase + 'JWwPbPnkWooIpKd5WYsdpfO3Mh14oA.png', u'ТРК Киев': logobase + 'qW0p5z3De7COmSxTmvJ4ZA2wOuSJjg.png', u'ТРК Украина': logobase + '0co3dwhFDhoCVeTbfMV8ASYFYxSrWM.png', u'ТРО Союза': logobase + 'xAXy9iMyJ4wa2wmugJvbZuDIzc9pVz.png', @@ -581,23 +551,21 @@ u'Улыбка ребенка': logobase + 'P8aPFN50uJWJHkrqFGb7wgzfaTHUOO.png', u'Унiан': logobase + 'fhpFrTDoI9xx7UlK65KAjAbdTGehLL.png', u'Усадьба': logobase + '5yIxLQzQyZnH5EJcwpSGb28QuRTSFH.png', - u'Успех': logobase + 'RLcfsouYRxTNrQT97AOPIYfSneJyB6.png', u'Феникс+ Кино': logobase + 'idiNkkBsxLwxWCF2VZrc9LQEevKh0d.png', u'Футбол': logobase + '472.png', u'Футбол 1 (Украина)': logobase + 'AMKtYwcgSAX5mTcPdhQDe4he18Jz7S.png', - u'Футбол 1 HD (Украина)': logobase + 'hZaWPKLVxTqUWZk0LTmLi1K1WUzX85.png', + u'Футбол 1 HD': logobase + 'hZaWPKLVxTqUWZk0LTmLi1K1WUzX85.png', u'Футбол 2 (Украина)': logobase + 'PUXTI9mKcs49JnEENkh95KoKqt9VNg.png', - u'Футбол 2 HD (Украина)': logobase + 'TTvGrBoRM07MHY4q6bSwfzVuDKEGTi.png', + u'Футбол 2 HD': logobase + 'TTvGrBoRM07MHY4q6bSwfzVuDKEGTi.png', + u'Центральный канал': logobase + 'vzfLS14qVT0rSphoNeEuO2WDvXFoub.png', u'ЧГТРК Грозный': logobase + 'LqDNdQj6nf4MraZztT6ZnACn7yOJpV.png', u'Че': logobase + 'Hv36ZG48lg1mm2wdxAo3ju1EFS41Ga.png', u'Че (+2)': logobase + 'N7R86wgoPSELhZ9jCeqaykayjU8mbB.png', - u'Че (+4)': logobase + 'Hv36ZG48lg1mm2wdxAo3ju1EFS41Ga.png', u'ЧП-Инфо': logobase + 'Xy7mLl3exaBKuLlDGdrRls6hyR7mSw.png', u'Шансон ТВ': logobase + 'VY0TyCCkKOj5b8BhBJjT020sQoxL9F.png', u'Эгоист ТВ': logobase + 'moG8uExVh4nw3MN7dmGFdysJHBWLk6.png', u'Ю': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', u'Ю (+2)': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', - u'Ю (+7)': logobase + 'lS7OcLo9fsdDFdDFDvdlM3OE3Uu8Tj.png', u'Юмор ТВ': logobase + '6VFA1SVxeFHUsGaKPbNxWZREDkGeZw.png', u'Ямал Регион': logobase + 'xapccCaMjlT6JEAkZmk27wzCXlEU2m.png' } \ No newline at end of file From 26e79d1a1e4d97d92442d4dc77107432387d6ca4 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 21 Aug 2016 18:40:12 +0300 Subject: [PATCH 64/95] Added connection time to /stat --- acehttp.py | 2 ++ plugins/stat_plugin.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/acehttp.py b/acehttp.py index 698296c..6c78244 100755 --- a/acehttp.py +++ b/acehttp.py @@ -442,10 +442,12 @@ def __init__(self, cid, handler, channelName, channelIcon): self.channelIcon = channelIcon self.ace = None self.lock = threading.Condition(threading.Lock()) + self.connectionTime = time.time() self.queue = deque() def handle(self, shouldStart, url, fmt=None): logger = logging.getLogger("ClientHandler") + self.connectionTime = time.time() if shouldStart: self.ace._streamReaderState = 1 diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index 2fe346a..4cbbd77 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -4,7 +4,7 @@ To use it, go to http://127.0.0.1:8000/stat ''' from modules.PluginInterface import AceProxyPlugin - +import time class Stat(AceProxyPlugin): handlers = ('stat', 'favicon.ico') @@ -28,7 +28,7 @@ def handle(self, connection, headers_only=False): connection.wfile.write( '

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') connection.wfile.write( - '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') + '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') for i in self.stuff.clientcounter.clients: for c in self.stuff.clientcounter.clients[i]: connection.wfile.write('') + connection.wfile.write('') + connection.wfile.write('') connection.wfile.write('
') @@ -38,5 +38,6 @@ def handle(self, connection, headers_only=False): connection.wfile.write(c.channelName.encode('UTF8')) else: connection.wfile.write(i) - connection.wfile.write(' : ' + c.handler.clientip + '
' + c.handler.clientip + '' + time.strftime('%d %b %Y %H:%M:%S', time.localtime(c.connectionTime)) + '
') From a7ea229589684e543ae373aaf73c1e8d7d29fc73 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 21 Dec 2016 17:06:10 +0200 Subject: [PATCH 65/95] Correct exit from proxy if vlcuse=True --- acehttp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acehttp.py b/acehttp.py index 6c78244..2ed67ca 100755 --- a/acehttp.py +++ b/acehttp.py @@ -759,7 +759,7 @@ def findProcess(name): def clean_proc(): # Trying to close all spawned processes gracefully - if AceConfig.vlcspawn and isRunning(AceStuff.vlc): + if AceConfig.vlcuse and AceConfig.vlcspawn and isRunning(AceStuff.vlc): AceStuff.vlcclient.destroy() gevent.sleep(1) if isRunning(AceStuff.vlc): From 29ad70d389890fce75657c5a82a241b4dfceca5e Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 21 Dec 2016 17:11:29 +0200 Subject: [PATCH 66/95] Correct support for zone id --- plugins/torrenttv_api.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py index 20abb68..3fcb9e0 100755 --- a/plugins/torrenttv_api.py +++ b/plugins/torrenttv_api.py @@ -39,7 +39,7 @@ class TorrentTvApi(object): API_URL = 'http://1ttvapi.top/v3/' - def __init__(self, email, password, maxIdle, zoneid=None): + def __init__(self, email, password, maxIdle, zoneid='1'): self.email = email self.password = password self.maxIdle = maxIdle @@ -73,6 +73,11 @@ def auth(self): self.session = result['session'] self.lastActive = time.time() self.log.debug("New session created: " + self.session) + + req = TorrentTvApi.API_URL + 'set_zone.php?session=' + self.session + '&zone=' + self.zoneid + result = self._jsoncheck(json.loads(urllib2.urlopen(req, timeout=10).read())) + self.log.debug("HTTP streaming ZoneID set to : " + self.zoneid) + return self.session def translations(self, translation_type, raw=False): @@ -87,9 +92,6 @@ def translations(self, translation_type, raw=False): """ query = '&type=' + translation_type - if self.zoneid: - query += '&zone_id=' + self.zoneid - if raw: try: res = self._xmlresult('translation_list.php', query) From aac5f37b3914db2223840d0401fa527141d20660 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 21 Dec 2016 17:13:58 +0200 Subject: [PATCH 67/95] Add support for zone id --- plugins/torrenttv_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index eab25fc..da0b0a7 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -78,7 +78,7 @@ def downloadPlaylist(self): if self.updatelogos: try: - api = TorrentTvApi(p2pconfig.email, p2pconfig.password, p2pconfig.sessiontimeout) + api = TorrentTvApi(p2pconfig.email, p2pconfig.password, p2pconfig.sessiontimeout, p2pconfig.zoneid) translations = api.translations('all') logos = dict() From eda84b558e6de0b6a07ea30c7084d72beb207ff7 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 21 Dec 2016 17:15:24 +0200 Subject: [PATCH 68/95] Correct support for zone id --- plugins/config/p2pproxy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/config/p2pproxy.py b/plugins/config/p2pproxy.py index 9b2da83..d2f2163 100644 --- a/plugins/config/p2pproxy.py +++ b/plugins/config/p2pproxy.py @@ -39,6 +39,6 @@ # Format of the tvg-id tag or empty string tvgid='ttv%(id)s' -# Zone id - MSK, SPB, SAM, etc. +# Zone id - AUTO (1), MSK (2), SPB (3), SAM (4), AMS (5), ISR (6) etc. # For more details see http://api.torrent-tv.ru/v3/api_v3.html#toc-35- -zoneid=None +zoneid = '1' From 5ed0c08c21d0dd364e1d61d8e64ec92ce212130f Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 21 Dec 2016 17:18:31 +0200 Subject: [PATCH 69/95] Update picon's logobase --- plugins/config/torrenttv.py | 286 ++++++++++++++++++++++++++++++------ 1 file changed, 240 insertions(+), 46 deletions(-) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 8de8524..28088a9 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -23,26 +23,30 @@ logobase = 'http://torrent-tv.ru/uploads/' logomap = { u'0x0 Fireplace HD': logobase + 'H1VboxDJC7sE7x3nKXoYT0X5r4LIqD.png', - u'0x0 Music HD': logobase + 'hFj4tnC5uqAgpod3doHnJGZxgZXaiP.png', u'1 HD': logobase + 'FtLnmUwjG18XJFEKYvLKjwUq1gwHVZ.png', + u'1 Мистический': logobase + '6NZHK1Lz0SbqVMhy20L9gPEJjW89mQ.png', u'1+1': logobase + 'omm2Xc8xSVIT6Od6ca4QqMrEXw3jaK.png', - u'1+1 International': logobase + 'o6wtkD3LF7NT4yADrcoBrXVWDDrvgV.png', u'100% News': logobase + '9yEWvPmTcFS8lyQ5NjJ7vbYOa3bx1W.png', u'112 Украина': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'112 Украина HD': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', - u'12 Канал (Омск)': logobase + '6DFMdGjXUjvzyk3B5ltbEd8mWVxAcD.png', u'2+2': logobase + 'XHXBC3ghvhh100BNXylSgJLx5FVQgD.png', + u'24 TV': logobase + 'QkclX0M9kXSGGDRhBw8H0T37VDzwNb.png', u'24 Док': logobase + 'H1UXBai10DjYfScfv1sNAILV9EPDer.png', u'24 Украина': logobase + 'XfKEdfsy4S1zbE8n2tB1tNNe9IkrRP.png', u'2x2': logobase + 'hTpJUV15GSTxZ5kJQGLcn42kCzKyEH.png', u'2x2 (+2)': logobase + 'ZPOhZle6vrDaulo2KMmyrCkkkLn7Ci.png', + u'360 Tune Box': logobase + 'WofrwD5WhK8TxQvbTBchuo1QKcbbFS.png', u'360 градусов': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', u'360 градусов HD': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', u'365 Дней': logobase + '0IZrVwoxtmjtgnWu5Dj4Hb8FRc8NIX.png', u'3S TV': logobase + 'AL7uQOgsyYqE7V1BagxC2jqQVOvucp.png', u'43 Канал HD': logobase + '1mNoU1QXmcmK48eVTlnADtsEBmA2sN.png', + u'47 канал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'49 канал ( Новосибирск )': logobase + 'WK3XEVPHqpV3VkKEBHi8QSXoPKPj6T.png', u'5 канал (Украина)': logobase + '9La0uS6S8rMKr0BOh6vSCQLNiCqN7N.png', u'8 канал': logobase + 'wKPUJjcpZIfI5zBSZwNayOlv63zRNV.png', + u'8 канал (+3)': logobase + 'XaNnxrpV00f3Zcfyw6WAg6uMT8NznG.png', + u'8 телеканал ( Красноярск )': logobase + 'MmesR31Atj0JdU6q1MaqXXdm533h00.png', u'9 волна': logobase + '8zDeTSBhsmJDbXo9dxHA1c9mDOP9sP.png', u'9 канал Израиль': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', u'A-One UA': logobase + '8rEKNCXhKeWnUvGhWCsrb2RN5FMqJi.png', @@ -50,33 +54,39 @@ u'AMC': logobase + 'rozrC8ZPYA1YGajyow35doeVcaQzpa.png', u'Amedia 1': logobase + 'jT3vAEOG5jTd2t8GcC797Bw5W0kSl9.png', u'Amedia 2': logobase + 'fAvxTQbWu0DAcMkqej0m73KohAcQJw.png', + u'Amedia Hit': logobase + '3lKypp7zXsJ3FDusXzKw9hHnVLythX.png', u'Amedia Premium': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Amedia Premium HD': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Ani': logobase + 'vui1cRrE05CZv1N9Qb20jJ6mTFOJue.png', u'Animal Family HD': logobase + '58VN85gBIna4Ko2K2uNeG6ZrfFNTLK.png', u'Animal Planet': logobase + '45.png', + u'Animal Planet': logobase + '45.png', u'Animаl Planеt HD': logobase + '9HZGan5rQItVQOfnB91FGqyJXjoqYV.png', u'Anyday 3D': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', u'Anyday HD': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', + u'Arirang TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Armenia TV Satellite': logobase + 'M4UAMT6cEyL3zuim9fzy38vZRkIfKq.png', + u'ATR': logobase + 'uggqxQxxDTuz1P1mY4PZdlRcAKow3F.png', u'Az TV': logobase + 'FB2qxgNm8xTU6YiR2fY1jH4PIRR2SF.png', u'B4U Music UK': logobase + 'LlfDsq3FEU2D4P3lmj25fvl6zQnvNM.png', - u'BBC One': logobase + 'ofuDCyM7MgzwFbzG2pyavFu6xeRHwp.png', - u'BBC Two HD': logobase + 'bbUzq7qIDlw2K8zk0oRHZzc4yHJkW6.png', u'BBC World News': logobase + '8cSYWwvq6BAzcGDjJODvCGQYGJ2c4S.png', - u'beIN Sports 1 HD': logobase + 'lqnLsL3LkmnVte1I8djXkbFEYFx91o.png', u'Bloomberg': logobase + 'OTsoNZRT8xjXz5nnTICPCQRvNLjBal.png', u'Blue Hustler': logobase + 'dFDAUzpGhQFOyHz59iyi7gsGHHMQbj.png', + u'Bollywood HD': logobase + 'HXzWxtMxmXkrpgr88Kh8Z1A8F6o5dK.png', u'Boomerang': logobase + 'tsP2U3zkp5o8B0TD6luRLg0leS9FvM.png', + u'Boutique TV': logobase + 'bDSWVUfAEYN9VS25roxW52xO1pj6qp.png', + u'Brazzers TV': logobase + 'VPCmFsMfpnB0pg9VBzvUG5TkRcHTsR.png', u'Brazzers TV Europe': logobase + 'OmVBp3Kz4Lx722dq2e1OxE26QSxrDA.png', + u'Bridge HD': logobase + 'hSVhfaLPt58KBflQjQxwiDqqO6h3rm.png', u'Bridge TV': logobase + 'dPhBiaViIznwjeWU3pTbIXFmS3iJkU.png', u'Brodilo TV HD': logobase + '0pLPWiGqZjDe2O4vbPd8QL0qGsA9lq.png', - u'BTV': logobase + '52ZaTijyyDOsKI7B1fYnUWliW6i2ah.png', + u'BT Sport 1': logobase + 'whrGkbmFh1pcdxuanV5u4zcD7PDuhd.png', u'Business': logobase + 'wvwXc2x8Bjev90GD6LO6t7TL2aKW1h.png', u'C More Tennis': logobase + 'ql4qy7gHN4GtWhcqfd97HqXUs8HXI9.png', - u'C Music TV HD': logobase + 'YhtkJhsV8NKwm4FGWhQZDmYcW0TYQS.png', + u'Canal 3': logobase + 'rb39jMz38xLuXKGCuWD2GcHHMEzfX1.png', u'Canal+ Sport HD (France)': logobase + 'I4vGNtOsYtkBnwOwFi94RHazK1ngqw.png', u'Cartoon Network': logobase + 'NTNQLLri3Hh9iqYjW7VEkFYJsTLjk9.png', + u'CBC': logobase + 'GuGWslWHNQ2fb88q6JYKSAV1n9A6hF.png', u'CBS Drama': logobase + '1Mcq9jbl7qPeXpT0bbPTnf8aM3f7dq.png', u'CBS Reality': logobase + 'QbWaD2vNgLRLY0Hsccg3nf9azAXRld.png', u'CCTV 9 Documentary': logobase + 'dqoArqO8dd09vzsSSQPqLUj5wM9O9M.png', @@ -91,15 +101,22 @@ u'Deutsche Welle': logobase + 'RnqBDfde1HP4OkZhXWlKB1xkHi6Io9.png', u'Digi Sport 1': logobase + 'gk0ajzoQjMHlBKM298kWjcfNy1P8ec.png', u'Digi Sport 2': logobase + 'x1HMd7tDoprxuZvq90Uj2Gb1eEOhTm.png', + u'Discovery Channel': logobase + '44pYdn5EiVvHLg0VtshNaBO6HBUosF.png', u'Discovery Channel': logobase + 'oKx1ImWVRT3AK3DHYWUVc71JZUkwu5.png', u'Discovery Channel HD': logobase + 'SmWnYlOvkJn8GzttT2UY0vmo8PYfMg.png', + u'Discovery HD Showcase': logobase + 'HCjNT859EyclaIl0GSueEiUacnR5Qd.png', u'Discovery Science': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', + u'Discovery Science HD': logobase + 'nnC8c8Z3iWbGSsgDORtWrS6O5n0ljr.png', u'Disney Channel': logobase + 'JxEjTeXwExjnxutQGKJBmMI85tpNqK.png', + u'Disney Channel (+7)': logobase + 'LZQKp6qoXQ8Ef64qUzcCOACy8qJAoq.png', + u'Disney Channel +2': logobase + 'awk1MeZ3IP1HOAtPt4jfvdR2P6YqIc.png', u'Djing Animation HD': logobase + 'ZA8pwcclRpzTflNKLqeZmDHCA7lHoX.png', u'Djing Classic HD': logobase + 'fDCLK7k2nYRKPAD125JdHM5uuEM7bZ.png', u'Djing Ibiza HD': logobase + 'aGqPHXL6MxB8j4GXldadnOR3r5D03f.png', u'Djing Underground HD': logobase + 'ceUcUA6hsEV7tBVmSF1chE203B14M3.png', u'Dobro TV': logobase + 'tcHbtjkY9gYD4VqipgwLP2BYxgdrZb.png', + u'DocuBox HD': logobase + '3AIG2M0cHQuzeyQA2o1hClKAy1F8Ir.png', + u'Dog TV': logobase + 'NblfllG2mrqX3Zrb36rv7hLUe2sG1R.png', u'Dot Dance HD': logobase + 'Z8TDmebVzyNS57me2IO2rd2LaiNRM6.png', u'Dream TV': logobase + '3k3m3ElnUQ0UhX54qa7hOGbmNjKZSc.png', u'DTX': logobase + 'bx7MosYUikfuwwIOzx2elT6Dh7h50I.png', @@ -112,20 +129,44 @@ u'Eska Rock TV HD': logobase + 'VU2POxFhYCM4XhFAtOp8Y4sdlbu32M.png', u'Eska TV': logobase + '1KX19DgurgoaSKNTTpjXCeSQr1epwv.png', u'Eska Wawa TV HD': logobase + 'r9d697qox4MFgypnVqeILYWKcfbwNc.png', - u'ETV 2 (Эстония)': logobase + 'HExQnCel4LeRvVFpvXj7hXfMXslkGE.png', + u'Espreso TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ETV pluss (Эстония)': logobase + 'cZBJJjYDyuuJHMQk3ZOogcDZYcu2SK.png', + u'EU Music': logobase + 'hubnM8JT96ngUDKI6WBYv9aKMIyWvS.png', + u'EU Music HD': logobase + '1sHUUmgTa6QmeeXWeZ2mFfPZyHX4Mz.png', + u'Eureka HD': logobase + '2cuuRl39vFAHg1pjMKWJJoqwYOqo6y.png', u'EuroNews': logobase + 'Vb3fP5gUK0q40WuzYeUhMT7RQmDg27.png', u'Europa Plus TV': logobase + 'PkatgpdmA4ArsgSG5shu0ZCQQ5RgMx.png', u'Eurosport 1': logobase + 'QA9jgUaQRrE4vMno04eM3aUrklXOce.png', u'Eurosport 1 HD': logobase + 'DpFTzUEA3y67Z6ObTPF4xH0XLNRAZm.png', - u'Eurospоrt 2': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', + u'Eurosport 2': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', + u'Eurosport 2 HD': logobase + '0mo5WOW3xn7FmcIRM9PMZxb2Zgad86.png', + u'EWTN UK & Ireland': logobase + 'maP6bdwOGv77xHPvRnKBHww9cmG2oV.png', u'Exotica TV': logobase + 'K5hm3mURkkDaSc7RI5RDti5edynMGl.png', u'Extreme Sports': logobase + '21FhIqWK82JDPNuLTEIC9hSO2EHfks.png', u'FAP TV': logobase + 'd5ulbooZicKw3aMLwn2Ho0yD9c46T2.png', u'FAP TV 2': logobase + 'YVmUBY8cBPO8IoL4djlyeKCDbs6f0p.png', + u'FAP TV 3': logobase + 'S5gXPPcF53lFHQ3kgJofKMQudRQKPt.png', + u'FAP TV 4': logobase + 'xv1QyBV2OkaDbZ1ebRB2FWwfw40BR5.png', + u'FAP TV Amateur': logobase + 'LGfemDTp3Z3mvhn1aKSGVvtlNu5zMF.png', u'FAP TV Anal': logobase + '1PVgf85c4DjIsiWEikpEHDV1j1m6ph.png', + u'FAP TV BBW': logobase + 'PHnwbskpP4oixpMjlJKgGvTAajXFSg.png', + u'FAP TV Compilation': logobase + 'FXT4PI3HHWIGlk49jRpHyGQBTTVbmt.png', + u'FAP TV Gay': logobase + 'vJB3tfzXuDX8R7eLXV63tCFgxnOgP1.png', + u'FAP TV Legal Porno': logobase + 'QMv8xcYgTaW9108ymib6XyWXRMI7dn.png', + u'FAP TV Lesbian': logobase + 'M3LLmt76GGyLBhpAEnRtn368pAeGH3.png', + u'FAP TV Older': logobase + 'G1L5Qh2QVnSu6zEVua6KOI4hxtUfcn.png', + u'FAP TV Parody': logobase + 'mu4NIjgq7Xu5nt32zTSxGvBOfk90xt.png', + u'FAP TV Pissing': logobase + '3QJ9sSAI90ALizvl1B2sbt8WxIVLSu.png', + u'FAP TV Teaching': logobase + '3MzvfwDLSX8zUmmuO7TpS8O2iD03Tj.png', + u'FAP TV Teens': logobase + '7Z9gyzosm1oUC82aFfA3mBG9YAjOrS.png', + u'FAP TV Trans': logobase + '05P2Z8lWWIgaEmvWcTRO1R9F0lMPZL.png', u'Fashion One HD': logobase + 'iPs2ptiBXm8h0KSnRmyqu45texHNig.png', u'Fashion TV': logobase + 'PSjqabjhYIBqcS8hUA8WNrZEjV4zZY.png', + u'FashionBox': logobase + 'bNAHBFlVPUSr7PI5bPQVXQcJiOcjd2.png', + u'Fast and FunBox HD': logobase + 'D88mJMen7WRxrg0C7TFFeiXXI1gRHM.png', + u'FightBox HD': logobase + 'mZbmQtneRV9cWJsMQ2PSfkGqbsJm9o.png', + u'FilmBox': logobase + 'XVIhYgI4sM1faDqfgMxWqbuOew9bn4.png', + u'FilmBox Arthouse': logobase + 'aZIcmFuP4mihAQoNXEK05oglDfhupR.png', u'Fine Living': logobase + '22I1fK1aMCUgeYUCV0K3vwmlJKELZD.png', u'Food Network': logobase + 'W68LEGlOOMPtN0qoGXvdkWhHBjKet6.png', u'Fox': logobase + 'XGC77wQNeEyaJ2z2mDipyIPsoF0xc1.png', @@ -135,41 +176,56 @@ u'Fоx HD': logobase + 'Pl8S60EJ52htHxi1gAw1SS1y8i1p3z.png', u'Fоx Life HD': logobase + 'Vou521VpOGAGqhp4HUfiG7BSbKNSk6.png', u'Galaxy TV': logobase + 'pU2NsRP9CVtEgQuDTi9jcTYV8iAD4a.png', + u'Gamanoid': logobase + 'DxEQkipHmJjSY0wHGKHDV6eAZKiwoG.png', u'Game Show': logobase + 'Uc9sBHj0DAYzfXMZmdxeriCBZvUpeb.png', + u'Game Show': logobase + 'WgebxxZr1mTHsGtMTYZzLvV3OnPQrQ.png', + u'Game Show-HD': logobase + 'sB7MetJbNWRBt2Hk56gffI5F9hraLt.png', u'Ginger HD': logobase + 'eW7CqWW2bppbpKzhNctyElyv5Nzs30.png', u'Glazella 3D': logobase + '7EM3eHww2EvOf9jgeFPdZrsQwO8Fz7.png', u'Glazella HD': logobase + '7EM3eHww2EvOf9jgeFPdZrsQwO8Fz7.png', u'GlobalStar TV': logobase + 'Y4kNxgbAnPGCt753P73NzCIbcNz5lD.png', u'Gulli': logobase + '2IynBdmw3mmdXt01r8DXKxeor7STnw.png', u'HardLife TV': logobase + 'fHPc5oaRdIzKpHTqBFnHA4O2LVf91D.png', + u'HD Fashion': logobase + 'EU8qD4hyW8aJjCdSYOVNOMk8WP02vw.png', u'HD Fashion (SD)': logobase + 'nEIXShrZ56g4IA4VKXReUBF32iYwKJ.png', + u'HD Fashion ukr': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'HD Life': logobase + 'jUteUS0xRGdvLyBVqNjowEUDkOjT0t.png', u'HD Media': logobase + 'woAI3zcytfbyiX0LBRToKzErJNy1qF.png', u'HD Media 3D': logobase + 'takNty6pirY7TeCRPX5VyUW1mZHHxI.png', - u'HD Кино': logobase + 'iHVs7YvUVlUvMTnlma7GMpX5p0Tpy1.png', u'Hello TV HD': logobase + 'o4WXPUTr5f2tuXFdjuLqgEg3qieGre.png', u'History Channel': logobase + '9cVifexiWW0qWDhhpnLNVydoZkeRqZ.png', u'History Channel HD': logobase + 'VFgU260pmiIyPxCzD3f8R7Yc6DXClH.png', u'Hit TV': logobase + 'FnCnW1vmvm8gjAwMGXe2Q8qtN1C3zy.png', u'Hustler HD': logobase + 'LBz8ia8AASewVuLjMs5v4MDiVYfsJO.png', u'Hustler TV': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', + u'iConcerts HD': logobase + 'fLNDK6Nxz7xMv61nOsZvED749DlOtz.png', + u'Ictimai': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ICTV': logobase + 'YuNtYxhj9vqgTU9kVuz9imhqviY4PZ.png', u'ID Xtra': logobase + 'fq3ovkZeu4R4YVYt1VMZN1NU7mMrH9.png', u'Idman TV': logobase + 'UhhUZdtM7FjDFbmoleWalj7tp6nj5o.png', + u'Iland': logobase + 'S2eR34Z8l9ne1lteBOLvknOq64jMJN.png', u'InterAz': logobase + 'rdntbmpYEYtyRbW3nGa1FUNZPXF53O.png', + u'Islam Channel': logobase + 'XqP875iZVDQXcyNB4AnAEZ4n3PX9xs.png', u'Jahonnamo': logobase + 'OqNuJdvRTdBh5NeydnpmCfMnTJs9XZ.png', u'Jasmin TV': logobase + 'MBRUFcRx5wtHLZmgahvcGdXB2ERdst.png', u'JimJam': logobase + 'BPDFCK5SQF3mXu5MsDNSdtvz4Gjawo.png', - u'Kino Space TV': logobase + '28A3JkU9kVHQWimTbBinKpqYyjZHcA.png', + u'Juce TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Kidzone': logobase + 'CMwzzYODhlLDC188nnF28qxfwvMkWa.png', + u'Kino Space TV HD': logobase + '28A3JkU9kVHQWimTbBinKpqYyjZHcA.png', u'KroneHIT TV HD': logobase + 'dq6m0UE9ffgAbXNt49qDlIAr7T8lud.png', + u'Kvartal TV': logobase + 'HeyS7MiMm71GffX6GeOFDMIs2NOgLv.png', + u'Kvartal TV': logobase + 'zkg5FwPBQonqxQlFIOblrboFQpHK6i.png', u'Lale': logobase + '99AZ5VwFy9yZrgsdZ6kIxcoYqwSyUH.png', + u'Lale': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Lider TV': logobase + 'LByLhdeQ30Ln5pSZRYSpoLpXQS5Ytr.png', u'Life78': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', u'Life78 HD': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', u'LifeNews': logobase + 'K8dBCUNz1BSwbgUYYU04i2qJpQKLMc.png', u'LifeNews HD': logobase + 'Mvurp6cp7Sq2fV3tnFBwPtJy7Ifm1i.png', - u'LNK': logobase + 'svzmHjJUDAOijKSBd3HL9XTvBOBPij.png', - u'LRT': logobase + 'sgH7Pdrd27V3cvlvg8OFOzsL2gLbcF.png', + u'LTV World': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Luxury World': logobase + 'QvaV83TwgWHnaGmw4C822MCNYi9zio.png', + u'Luxury World': logobase + 'LB9Pgt6QNYOVDEJqKI2xcmq6VZOP0m.png', + u'Lviv TV': logobase + 'VMl4S9yd5IYFAVFaR8XG6KEm5pgVu4.png', u'Maxxi-TV': logobase + 'uqyYr07PPk2OuEdnNrbkMHVlGZCJp5.png', u'MCM Top': logobase + 'b4SLIxXFbKUErZxBXfGoo6eQN5Mu38.png', u'Mezzo': logobase + 'GUzJ4cyK50ZahOSIx6tTbwGLJiUnSA.png', @@ -186,34 +242,47 @@ u'Music Box RU': logobase + 'zaHCW7nPCyGRnqHDkenCIXo7d6vR7v.png', u'Music Box TV': logobase + 'fvt4pris0lwnVhSyUrh8QlyzWBhbgz.png', u'Music Box UA': logobase + '8T7Rnct8q2VHhRm0BcGFxCjxuYEArc.png', + u'Music TV': logobase + 'XQB0y9UMs7XT4YTtKd8AcYigdhP3Wv.png', u'Mute TV 3D': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', u'Mute TV HD': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', - u'N24 HD (Germany)': logobase + 'j0NiKCcpOnPBobWAE0CVBLsBhSLgCt.png', + u'MUTV': logobase + 'yDbkEj466sOzY5cCPVbCW6NIT7kkly.png', + u'N4 TV': logobase + 'R1NDSrUKnB1RoT8iv1okSe2xY39W4J.png', + u'Nashe Music HD': logobase + 'Xv91mHlQ4d5fCWu7xxpgy847qTWyc2.png', u'Nat Geo Wild': logobase + 'ciHIDUuHEnkEuPghbcqkDQx4vadle3.png', u'Nat Geo Wild HD': logobase + 'YYa1wyNA9prFK1APZ2ZSHGirPpm8kY.png', u'National Geographic': logobase + 'i6STSw6Hg1wWP18yBAOyKoKpSMeKLu.png', u'National Geographic HD': logobase + 'hK1waimMq9eAp0ugM19moSoQvUeve5.png', + u'NBA HD': logobase + 'ZfUh18TUU7KVCgzPPi79D5Zqtu8njC.png', u'News One': logobase + 'SnaQqBa1YMS2b8b8EGdZSMCConLbDS.png', u'NHK World TV': logobase + 'JJ8Sh9c7zA3PaXlK1ZaNjy3GgWQFh2.png', u'Nick Jr.': logobase + 'D87kJ3fIWIm5wKi5qxm24nbuPQv0U8.png', u'Nickelodeon': logobase + 'j66xpaZbfiYIgQxv76QAPckPVjmLNs.png', u'Nickelodeon HD': logobase + 'CFYx7Bd1aDSgkqjMLLQjFE6xe3u1E0.png', - u'Nova Sport': logobase + '8npwMe6SAEgj5nMBCoVPCemX9fKOvI.png', - u'nSport+ (Poland)': logobase + 'SVdxnEDeE280C8YVqsEIsJermTq1Ks.png', u'Nuart TV': logobase + 'aqMIuUixqLQYmJITPnOGtFkRPuTqKa.png', u'O-la-la': logobase + '7ddvb8Tivq7yuEgffrKildHi2BQcCE.png', u'O-TV': logobase + 'UlIt4rE3FZs7IQmPN3tsGS6fqY5F1D.png', u'Ocean-TV': logobase + 'cvBAngU16nJU1bEzxAEcMPiPvf7fVT.png', + u'OSN Movies Action HD': logobase + 'BUJiIaGHNzhRASjqNAgRIoC8Xz2Ket.png', + u'OSN Movies HD': logobase + 'UMzAVPfEGT4NxDvqA2nZrv61IFh4Bs.png', u'Outdoor Channel': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', + u'Outdoor HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'PanArmenian': logobase + 'Goi0hXa9CeThenpkvfPq7E961TMUAT.png', u'Paramount Channel': logobase + 'Iyzjhb5BBRKfvEZdBwNJ785jzroODU.png', + u'PARAMOUNT CHANNEL HD': logobase + 'LD6lUAlPHwEUOGeqDfa4xBfCdZY8nC.png', u'Paramount Comedy': logobase + '5EtvAWXB7VK1Yw82yvO28sY28dU4ZC.png', + u'Paramount Comedy HD': logobase + 'xN7JbuSQbfkVaI4fVG4XuwRDjs85Hk.png', u'PassionXXX': logobase + 'U2oO3j1eda31nrTwHqaQ8psrLfH5CD.png', + u'Phoenix CNE': logobase + '4A3PI1xIjIeECNSMDDvOfwXd5n2sso.png', + u'Phoenix Marie TV': logobase + 'mC9hfoTbTEXlNXEVFrJvXaj78QGUhA.png', u'Pink O TV': logobase + 'aI99kH6bHY5Qt2ph4W1Nh0wxdzd1p8.png', u'Playboy TV': logobase + 'lIWBxmt5GDl9tg4KsQNtA0CuZWdOHH.png', u'Polonia 1': logobase + '3fzUyCBgUoJ6zqc92KSXh9g9utJEuO.png', u'Pro Все': logobase + 'OavKhOpBMn27qPDKJXXm5rgATgovgn.png', u'QTV': logobase + 'zmh1xStjBZGJ6U5g3xLoemD2oT3w3h.png', - u'R1': logobase + 'OducDuiwKukJTHXoQo49vuh62PvXK2.png', + u'R1': logobase + 'zI5CoA2qyiJYuXzqsXo0LBmJv9Tern.png', + u'Rai 1': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Rai 2': logobase + '9m3iFYefQC0919T6EfaIhIjiINBuEk.png', + u'RaiNews 24': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Real Madrid TV': logobase + 'KfoLG7goywcRhMMCc3sO8IVwhuATLp.png', u'Redlight HD': logobase + '5tFZcJtKAZRbXtKGDuLe8FZ3lK9LI5.png', u'Renome (Одесса)': logobase + 'uXenrBCtSwAsv31RtOAXdn3hXm3i5N.png', @@ -221,7 +290,6 @@ u'RMC TV': logobase + '3CiAgachWg7ohgoU1Gilcm73hXhT41.png', u'RTG HD': logobase + 'g4MDyw0yqXWkIar8eH0cgCz4xhiKON.png', u'RTG TV': logobase + 'IeaOjwR6Q9eJjGZr0LYk2tpchM3ITZ.png', - u'RTL': logobase + 'Ekttfk03O2aNYv6OoYlVFEAykjDzom.png', u'RTVi': logobase + 'QBnba0xrWtpPWLL4yKDRixaCRQAmaP.png', u'RU TV': logobase + '161.png', u'Rusong TV': logobase + '186WxZMn3PGQyMlWsItM9JkSS4Tt29.png', @@ -231,53 +299,70 @@ u'Russia Today Doc HD': logobase + 'b8QePfFi6zsCDS7hfTeFWES5UN4SAk.png', u'Russia Today Espanol': logobase + 'zkPwjfFbktNO8EYDaHlYhwAeT88dmY.png', u'Russia Today HD': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', + u'Rustavi2': logobase + 'WAgaH51sY7ujcWlPQGWeUCP12zIU8t.png', + u'Rytas TV': logobase + 'TBousteH8Slv9wtLiQ0ssNHXslA4yl.png', u'Satisfaction HD': logobase + 'isdNgbfGENuaDPSMzsz8WMjBzc1rah.png', u'SCT': logobase + 'bDRN0guXHaNHruxtyFkpKYz8J09Xj3.png', u'SET': logobase + 'tKzOKIcOQYrFl1VL8B0QqERFXCAYfU.png', u'SET HD': logobase + 'sX1unYoKj8JR7m8lbtkmPCClRrjAZ9.png', + u'Setanta Sports + HD': logobase + 'jW7pJhmebW2fZsXUTvDuRQLahgiLlU.png', + u'Setanta Sports Eurasia HD': logobase + '1wgHdJP76TCItF14FxDwBtak8tmxRv.png', u'Sextosenso': logobase + 'HXMvFMLO9weHUcolVmmMKpz98T0K4K.png', u'Shant TV': logobase + 'IgJpH1JzyjI55Ki2G2Ybz6jzOkwG0T.png', u'Shop 24': logobase + 'bCuxLyvoTk8l5cBRSyKFXjWjYucvlu.png', u'Shopping Live': logobase + 'HVwxC489SYFr8Ttqs1he9RZjEmJjPn.png', - u'Sky Sport 2 HD DE': logobase + '4dPVkJ96LwrNCbFCii8dJwJC1wEAzJ.png', + u'SHOPping-TV (Ukraine)': logobase + 'Yhipu86vXMGf0hxCTXLezpGpcUbibW.png', + u'Sky Sports 1': logobase + 'eFxo8gGgylCVPEv9IG4Pud4LdmfAhF.png', u'Sky Sports 1 HD': logobase + 'U4ngWpMf0MTbeOpX4Tgxelz0tPCBs8.png', u'Sky Sports 2 HD': logobase + 'B40l6w5yVEpOpZ31io1VzkubFnygLT.png', - u'Sky Sports 3 HD': logobase + '5Ft6qyJQXmV1MogsjpO79oKKruQj64.png', - u'Sky Sports F1': logobase + 'J9T7KUE84YobHuNThXN6Hl9UG21tD9.png', + u'Sky Sports 5': logobase + 'PL0v2id4iRQoYNIhS4BVyKSPeL8Hor.png', + u'Sky Sports News HQ': logobase + 'v4uIda6tAEaPiumlAg2kqbxlyAheDj.png', u'Sony Sci-Fi': logobase + 'zLWEgf9BbxBr1TR2Qj2NxVQBuPecEP.png', u'Sony Turbo': logobase + 'CaPjVaQrpyN138TarQ7CYBqBOz0ZF7.png', u'Sport 1 Cz': logobase + 'kCLlfkFz3Ba3BL9Jc9ZPgUKXh2piyv.png', u'Sport 2 Cz': logobase + 'YLmEjnczWQGJcZC0SxRcH4ifPcwYlx.png', u'STV': logobase + 'DbKEKL5gUOFHiruYRjY2H9gTLOV5mu.png', u'Super Tennis HD': logobase + 'mjQW91VJdjIEhADvOO2s6OiKNeUdUK.png', + u'Super TV': logobase + 'iRrgl4xiCvibdxWfiQcVUWuYCYCI9K.png', + u'TBN Europe': logobase + 'bZT2NYEtReHVOSFTcW8xuDTVDvcoUn.png', + u'Tele 5': logobase + 'BahbW0uBxpVOHayoLrr6uFcaBsiYSc.png', + u'Tele Vsesvit': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Telemarket': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Telesur': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Teletravel HD': logobase + '4ZlASq3oDpOjXfhwluOzY74sy9elaE.png', + u'TG 3': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'TGirls TV': logobase + 'FufZ2heFswzvAbRkTQZs8UJBYGsxuG.png', u'TiJi': logobase + 'mD3GW0E7rdPwc4stjk7xrLI2gZn4Hq.png', u'TLC': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', u'TLC HD': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', + u'TMB RU ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Tonis': logobase + 'f4NBZiozbHDSxPGRpphIXhvlBjytkG.png', u'Tonis': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', + u'Tonis HD': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', u'Top Shop': logobase + 'uD257Lhw7Ko2YD1reC0nRqW7lpy93D.png', u'Topsong TV': logobase + 'DsJRpcbI6rgjONbQftC5nHt1XMAXYQ.png', + u'Trace Urban HD': logobase + 'v7sxI2w7167Ku0bd9eitaNW508A7Ot.png', u'Travel + adventure': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', u'Travel + adventure HD': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', u'Travel Channel': logobase + 'fhmrYjlpC0YMxFd2RqOolbjlXMr0tI.png', u'Travel Channel HD': logobase + 'zfnAGLCvIu1fx9hfrITAZMoo9HYww4.png', + u'TRT Türk': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'TSN 1': logobase + 'GJsJeixsOKvFaXz8XKqvX4Uh6sUicu.png', u'TV 1000': logobase + 'WJMEvVafVakrm7BUMy1lzku7VQCx25.png', u'TV 1000 Action East': logobase + 'GblbxkDGXZyW5oWt9W8wuERQAiZ7ZT.png', u'TV 1000 Русское кино': logobase + 'ch5DX6f8hxDnmyzrjotUoKHNGzcw9P.png', u'TV Bakhoristan': logobase + 'LoXaN929SQC5r5aQ3JETDXwG6VlPMk.png', + u'TV Mall': logobase + 'hao8n2RWJzxwNaiVHUawwkNVG6X4Zp.png', + u'TV Rus': logobase + 'MgGR3UkXL6TDO1kA9E9xxH7EYvEwu9.png', u'TV Safina': logobase + 'mJUmNhJbQqcr2NPppAryEJqDPBJGV0.png', u'TV Sale': logobase + 'hs0YdiUTlpRtb3wTiP4cXboX0H9oTN.png', u'TV XXI (TV21)': logobase + 'TKchoTWZFRMmGDBok08zoEFJ8mJJCe.png', - u'TV1 LT': logobase + 'CXD4Zq54oHMsByuJbWtdIqzD24dVVo.png', u'TV1000 Comedy HD': logobase + 'ygGiR2hkQLySH6khdo8GV9CyMJ8dXi.png', u'TV1000 Megahit HD': logobase + 'lVPY7WCjn1WM6NL6tfLFy8iGA4yk3Z.png', u'TV1000 Premium HD': logobase + 'raoDrpin8VKmi522LZWzSF0fLRO04m.png', - u'TV3 LT': logobase + '0OYDSD4Gc81rVyDm8YbqvY5gY0dNzZ.png', u'TV5 Monde Europe': logobase + 'ko7rbRBnyK1iINkLOA2adRvgVOEgUK.png', - u'TV6': logobase + 'l1UpPzlU6nnHGIAdtNGTgns5V2l6jV.png', - u'TVP Sport': logobase + 'AkCqdaRAZAZY0PKGoKZkhDlBCF9RR6.png', + u'TVC 21': logobase + 't6oWXfHEIMAjJ9GSEChuI2JHQg7KzL.png', + u'TVM Channel': logobase + 'aCAKhUCXdMfvBlPD4sHnmOe0LNpPB5.png', u'TVT 1': logobase + 'CKKdhDfmno9O52tMfWptiAQT0IBWV8.png', u'UA:Перший (Украина)': logobase + 'iN5vheceBnPmO18uXHGgKwq7q2PHtF.png', u'UBR': logobase + 'F6EzmjkOBVB0gmn1kQX6itv5VvFml5.png', @@ -286,6 +371,7 @@ u'Viasat Explore': logobase + 'uCqpsdKP0ialUUYxUk2fXshYdYfxzW.png', u'Viasat Fotboll': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', u'Viasat Fotboll HD': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', + u'Viasat Golf': logobase + 'IGpQl5iTxaDEPyKffdDEpU0EU0SPiO.png', u'Viasat History': logobase + 'MWGbB8wJp5Gm4vbPHl0ktohDDjMKdr.png', u'Viasat Hockey': logobase + 'CuAbCRGdf3Z1FGFiwErTbHZ3lAMJzr.png', u'Viasat Motor': logobase + 'RuYtGxEpqJ5DG7WxGCMWNDXosRdh59.png', @@ -295,52 +381,73 @@ u'Viasat Sport Baltic': logobase + 'ZIITckvF1w5u1MlubmhoG45HxPgcZZ.png', u'Viasat Sport HD': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat Sport Sverige': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', + u'Vintage TV': logobase + 'mhFKSRmQIsgPnIUCWFzWbMAXK5dn3i.png', u'Virgin Radio TV': logobase + '6DgkEMl3HtkpKQbzVovPdSYhy9f3ne.png', - u'VIVA DE': logobase + 'HagNMshKtJ7zKnk9fdmBLhITjoWdrJ.png', + u'Vision Norden': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'VOX DE': logobase + 'sfwtJrwyJw1vyIG83Ym08tm5pYIH2Q.png', u'WBC HD': logobase + 'fMdJvc8moH4HGH5OYqsZwskCowQYuE.png', u'World Fashion': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', - u'World Fashion HD': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', - u'XXL': logobase + '6nJtj85PlL0MxB8RDkM3toyGND3Anc.png', u'ZDF': logobase + '5SH5FeZiITw27CPxscjksZp272u7He.png', + u'ZIK Ukraine': logobase + 'dB1PmpYtgeI0C4u7Cv1QoXHlvc4JtT.png', u'Zoom': logobase + 'SyisYhg411o7z9kXci4vfpLq4KBZZ4.png', u'Авто 24': logobase + 'GZwKgvkC0vfYquFAn1dKhppNxdDit9.png', u'Авто плюс': logobase + 'WkRxjy6fJEBJ5NZiaGn2j05eqfFfQq.png', + u'АРТ 24 (Одесса)': logobase + 'H7YvrLp7Zwdb0G4rmB99E5nF4TTmsf.png', + u'Архыз24': logobase + 'tuMbRlnkeMiYQ6u9oeiAVfHF0F20RB.png', + u'Башкир ТВ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Беларусь 24': logobase + 'GxA1KJP5YwpWc38BoPEmLwQH6uDeEz.png', + u'Беларусь 4': logobase + 'xTwlFgOsv50ls1Z9ecK0MhC0hCGX2W.png', u'Беларусь 5': logobase + '2bzJLE9Sbk9BqUBlsVrtqpsz8h0rB6.png', u'Белсат ТВ': logobase + '9VYuUQxx1ss7ieu2upENtlibyamBP0.png', u'Бигуди': logobase + 'JvcMdB5e6KVBpbXT12ulzmDqenheRx.png', u'Бобер': logobase + '2Edln8vEbg7UUSVUo7lIJPR780OWAR.png', + u'Брянская губерния': logobase + 'V5M08NU3jRbd2wKvtVLIRbg9J6ObAZ.png', u'БСТ': logobase + '05dZ8fzOmf1lXYue2OvVsQ21eIeX69.png', + u'БСТ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Винтаж ТВ': logobase + '6yngbEKfgy6XtMw28INQCoOdulf2tC.png', u'Вместе РФ': logobase + 'qa50GYekwBWym7KtoJdzrWHWqN8TeU.png', + u'Возрождение ТВ': logobase + 'CKWaxJsLQYiJmnkLhtysIn8hb0NK8b.png', + u'Война и Мир': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Вопросы и ответы': logobase + 'xbV8M35FkvpieQ3TUEL8fhwU8MzjmQ.png', u'Время': logobase + 'F44yKDJQLsX0llpZ2wupg8V5vHx5fF.png', u'ВТВ': logobase + 'svsUD6TinXyv3B1q5sZf3fI9ebmpaF.png', + u'Глазами туриста': logobase + '0tuGdZaluGh8jDJulzD0MOcHOkKMJI.png', + u'Глас': logobase + '6m1xUI7vFyadFMFxXeIZjlATt7rlWw.png', u'Горизонт': logobase + 'f6wqkzO4WZW7D5Z9xIB4VkMRGmgXUU.png', u'Громадське ТБ HD': logobase + 'Ovkd9TiVv3nLcKPwQS2wkJ85KyYCMQ.png', + u'Дача': logobase + 'mX1irJVUyLtrXr9vfDdhknRqiqV9wA.png', u'Детский': logobase + 'jk8kody2p38CKdj5KGXWMwRLjgFIlG.png', u'Детский мир': logobase + '00Vf3rPABNnbNQ6Rv0dnfcg3JsJelA.png', u'Дождь': logobase + '381.png', u'Дом кино': logobase + 'jlC78Fy13KWjQUN6l3FtbsRLZDvc0x.png', - u'Дом кино Премиум': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', u'Дом кино Премиум HD': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', u'Домашние животные': logobase + 'HiWAmn5RvUKNJnSW2Jhxjs6maoNFV7.png', u'Домашний': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', + u'Домашний (+2)': logobase + 'EbGoVedT7WolQo811cBniNuONJG18q.png', + u'Домашний (+7)': logobase + 'n1Qul20d4Ux9bA8uT6iujBL4B93UO1.png', + u'Домашний (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Домашний +4': logobase + 'atZkgcX9EReSy2VkatZGbdQyBI2viX.png', + u'Домашний магазин': logobase + 'yhzajV7vdLogQzMKat0rLJWaDkPpTP.png', u'Донбас': logobase + 'vsj1IA3Z8QVL3AzzuN4EshgT4LUmRr.png', u'Драйв ТВ': logobase + 'pmmgMKcRbxeYkjVUVr4IAWM0UuZHO4.png', u'Еврокино': logobase + '34mszCG0j0Vf6kFcMrLPnFEA8UPdu6.png', + u'Евроновости': logobase + 'K6qQrjlS6ykRlhYGyagaK3jqSXmctx.png', + u'ЕГЭ ТВ': logobase + 'lkZvzcsrdp5eJ4359OcNa5DHaHFEtE.png', u'Еда HD': logobase + 'ojUD1jhpv7HBOLmubpEBOsANkpYNtk.png', u'Еда ТВ': logobase + 'TWdAdMXfMSylb2mQ4efFnOAYosymNC.png', - u'Ескулап TV': logobase + 'onT5lg0NOm9BSORR7KNsO1oRA9pY1S.png', u'Еспресо ТВ': logobase + 'lOwm890F5URuR5Ej7IacerzECPIDt4.png', + u'Етно HD': logobase + '3SMfgieNX8dCez4flcM8TpCCePrgaq.png', u'Живая Планета': logobase + 'xgKSMwqBdEyXnbVgb8LtNXSMiaPcOx.png', u'Живи': logobase + 'cOluSjslxxs3JZtSVO8c15xh7h8SDU.png', u'Загородная жизнь': logobase + 'cGGo8HRkVhy66UXKXZ4tH5HyUaaxJA.png', u'Звезда': logobase + '0HLRrFHt2QIkbJpLc1fy0RVe7hqCEC.png', + u'Звезда (+2)': logobase + 'wa6skHc8W2MRz9Mm3qoKS4LpctSvYs.png', + u'Здоровое ТВ': logobase + '6x1zbASjWqScFk0bghS2RX3H2k5XZf.png', u'Зоо ТВ': logobase + 'RtAhntWPlKQs6CIYAb72piNF9EsN3E.png', u'Зоопарк': logobase + '1Ugpb5T1THFcFpn19Mnua21KxHkjct.png', u'Иллюзион+': logobase + '8LToTNvWRBHvb5IKoteKm8EwAGw8mv.png', u'Индиго': logobase + 'dFIp5shmC5DbfWIDVaFh7coAofmLON.png', - u'Индия': logobase + 'XVWyHt5bFFcZNzmysBSjuVdGBGl45D.png', + u'ИНДИЙСКОЕ КИНО': logobase + 'y8wvb5nc77vn5c4x8kinv85c7mv6n875c84.png', u'Интер': logobase + '3SP67FapzyZqMVZTPiJIcN09KRkTeu.png', u'Интер+': logobase + 'QEdaDBbqr13CCfwKQAP77UZYPQIPn0.png', u'Интерра ТВ': logobase + 'Akn9ntxqkSNrX3BTFRRtXPaEYBszjR.png', @@ -349,30 +456,43 @@ u'К1': logobase + 'mk2mYb28HFIxkFIiMNQWmKUdn1Y8hD.png', u'К2': logobase + 'IjG76jf8k8HTNLooNpUiEXtkPfA2rG.png', u'Калейдоскоп ТВ': logobase + 'YJMqmzlZ87QeXYm9XpjH0XzpjljNcU.png', - u'Карапуз ТВ HD': logobase + 'ieXzw5xeahW3OoO66m2LX8GwLCYyYl.png', u'Карусель': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', + u'Карусель (+3)': logobase + 'cYXQq7FnkEBAyYxfoPPsL2y3fwMDKU.png', + u'Карусель (+3)': logobase + 'lQ7gn9ZjH9TS7X5JZnEZpJf4Jy5D2D.png', + u'Карусель (+7)': logobase + 'TxPMnikZ6AeVKgUH1TK3OADh9nalHw.png', u'Карусель International': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', + u'КВН ТВ': logobase + 'xb1VGuwpJFlQ9fWExWngUeSUSC2xJW.png', u'Киевская Русь': logobase + 'C1AZimW2NnNA17H1uJLxxePUMTPQZ7.png', + u'Кино 2': logobase + '4liS5ZApeFUGXcGL6gFrbw7swx2ZaB.png', u'Кино ТВ': logobase + 'KkITMDICqC1erWdSqyOqoccqde2wHC.png', - u'Кино ТВ HD': logobase + 'glnk0UNz0DwfqxgJh4A5Emo0tKrQAZ.png', + u'КИНОКОМЕДИЯ': logobase + 'y087hci8ityixcnyxinxoafhiu.png', + u'КИНОМИКС': logobase + 'y6b876ih8g7R876hfug3897wrhj.png', u'Кинопоказ': logobase + 'v0JEbxExcFI8dVEzCkpZUoktgiS9t7.png', u'Кинопоказ 1 HD': logobase + 'pNA1vR3sPoovYNKzO4TU6NQcSXNqjk.png', u'Кинопоказ 2 HD': logobase + 'Yf2XKrtorOF2wSZ23Q9NHhL2MfOcqi.png', u'КиноПремиум HD': logobase + 'p580CRZ8bBS6dw3plMWhhxXSzQ59uS.png', - u'Комедия ТВ': logobase + 'L2MEpT2YePoDvmKRjYy6yyt5ssH1m4.png', + u'КИНОПРЕМЬЕРА HD': logobase + 'y4Ihg8o7ikgJHF768iJHFH76iu.png', + u'КИНОСВИДАНИЕ': logobase + 'y1876iuyf7645urjg56utfy.png', + u'КИНОСЕМЬЯ': logobase + 'y2oerfuhdj2108nbs4ev875esk47dn.png', + u'КИНОХИТ': logobase + 'y3igbjZ&LGyqDWGIASUY78AWID.png', u'Конный Мир': logobase + 'd8gVne1Em14rIfM6pSsiuufXZeYQqi.png', u'Кот ТВ': logobase + 'Rtdtc632nbBQ2TsymgXhkOt7nhytjW.png', u'Красная линия': logobase + 'I43S6jd5noclar0LlPJnyY8adonmUV.png', + u'КРИК-ТВ': logobase + 'z4Iv85kqJXWHQJ5475DNxOxlo9Ty5v.png', u'Круг': logobase + 'fbzJYZiRscBiEvaWEFmzvYzEHdaNN7.png', + u'Крым 1 HD': logobase + 'fVpBXMtyVEJ538kDDcWsezJbgcG8zA.png', + u'Крым 24': logobase + 'x4s9QQXR8KerO13nlkuEujzcv5ww5F.png', u'КТК': logobase + 'BhI4KURCZ3fAgCQcwQWn7vRCJQblH3.png', u'Кто есть кто': logobase + 'MwNkO3fXd6KefRdiGlOdOQ5q0Zu7kS.png', u'Кубань 24': logobase + 'CAAqiN96tQzFDdtz3vjrrgeIjAKqNq.png', u'Кубань 24 Орбита': logobase + 'FauvJxsKmI5a1fR62uSH9hJfHs5TCr.png', u'Культура Украина': logobase + 'pyKdve4YhoChQFGSha8J0FBWBf302a.png', u'Купи Дом ТВ HD': logobase + '7CyfedwVltdaMcZXD3Xx7wa50mZJom.png', + u'Курай ТВ': logobase + 'A0onEnFhSzqGxckeyyXsf0ZQ4R3Vc6.png', u'Кухня ТВ': logobase + 'G0WbVMphlP9oJ6KvHRfx0xDfhrF9Re.png', u'КХЛ HD': logobase + 'kRN7BwVtcdaXrU4Mdg24qhFAxjx9oZ.png', u'КХЛ ТВ': logobase + '216.png', + u'ЛДПР ТВ': logobase + '1keO0CoTmw0B3VZRe4TZnzfGdKDqSj.png', u'Любимое ТВ': logobase + 'qVREQyj0rN87jDZ1ylYW0sDvzfNp8p.png', u'Ля-минор': logobase + '8FJA3xMMHcrZuGifHViyVQLjVIem5u.png', u'М1': logobase + 'ezvu2ugYMGnZ968LlnjPw7VjqWIPeM.png', @@ -390,6 +510,12 @@ u'Матч! Планета': logobase + 'mWQ92sSmszzmjme9GTueLPCxagJGtl.png', u'Матч! Футбол 1': logobase + '9PM8M6cN21wQ3M5isVZgjNepzUI4Ry.png', u'Матч! Футбол 1 HD': logobase + '9PM8M6cN21wQ3M5isVZgjNepzUI4Ry.png', + u'Матч! Футбол 1 HD Резерв 1': logobase + 'HWZYoNrs7kQimfhyzT8V2d0WvBRbEr.png', + u'Матч! Футбол 1 HD Резерв 2': logobase + 'jJsAF2rxef4RpccQrmfPkIbJc9kS7Q.png', + u'Матч! Футбол 1 HD Резерв 3': logobase + 'Tpv0BbEi315Omo6MKh3ZiX5zTzc8OT.png', + u'Матч! Футбол 1 Резерв 1': logobase + 'CWit8nYXCcCsmB6KbIedlKwFWWTyZT.png', + u'Матч! Футбол 1 Резерв 2': logobase + 'n6kF2auPAk8bMH2zray20zP12QKL1Z.png', + u'Матч! Футбол 1 Резерв 3': logobase + 'ipK5bpcm9mR9COdolCiHV6csIndprl.png', u'Матч! Футбол 2': logobase + '8MA3WloO6RsWX8N7Ck5ugek2Kirf4B.png', u'Матч! Футбол 2 HD': logobase + '8MA3WloO6RsWX8N7Ck5ugek2Kirf4B.png', u'Матч! Футбол 3': logobase + 'OLHdmyfUev4mMX0OGniJrlUwHnMKOg.png', @@ -399,27 +525,32 @@ u'Мир (+3)': logobase + 'QxOYkz6f80IdhmC4RSHI1cMd32CqYZ.png', u'Мир 24': logobase + 'auv6717gJOWi0A2VoeDQaCsx9G1NOj.png', u'Мир HD': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', + u'Мир белогорья': logobase + 'JRU6TyiBAX5Fk84DebhKeSxg7ZkrEf.png', u'Мир сериала': logobase + 'n3zRAlCCBLl5WOeunWGpuMmfdvSJcW.png', + u'Мир сериала': logobase + 'XWDsU7aoUyPPoKfcX7WMYQheuzHJL0.png', + u'Мир Увлечений': logobase + 'KtELENmDesBwzAAryLgJPqwlr9m17z.png', u'Многосерийное ТВ': logobase + '4TMYdVpZYXafyIumuB5d7PrjFnslyT.png', u'Морской': logobase + 'uzlb3awoyNqvIcf6i35hTVqf7gvuqz.png', u'Москва 24': logobase + 'dZcmoqRoZLhCBh8BE4RnbQivuDY6hH.png', u'Москва Доверие': logobase + '9oPazhJQrGZcSN64ZOS3WjLwGmQIZy.png', u'Моя планета': logobase + 'Qa41eifERrD77xQsmpRGbeTq95Ldlv.png', - u'Мужское кино': logobase + 'PUDb8m2JFLndsPvb56tdH0V4RW0kZc.png', + u'МУЖСКОЕ КИНО': logobase + 'y997iu65e65h4w5d3s4dy.png', u'Мужской': logobase + '6YbhuWNqPKQWWsUGbBnSbAbm7IGssX.png', u'Муз ТВ': logobase + 'gttVvZmkAklbl2i0Mqy1MCzSCn7WiY.png', u'Музыка Первого': logobase + 'fD2Hnsq5BPMGvobLDMPZP049yNhBYt.png', u'Мульт': logobase + 'ZVzHvGF8mZ6RTsSh6aWsPbF1FBLjyp.png', u'Мультимания': logobase + '132.png', + u'Мультимания HD': logobase + 'xrR3K6dBvelv1nZos2kTbTiU3QnJVK.png', u'Надежда': logobase + 'fvCkzRJPsTx4HONWPv3vPMjQNloPBT.png', u'Нано ТВ': logobase + 'QuURIfJUmXegxsHMYqMivVwxizbfKd.png', u'Наука 2.0': logobase + 'ypWbqYqKApM8cnDK1FibvQgpmgEay9.png', u'Наше HD': logobase + 'NyV9o2C4xIMFxxYUQsS1qsdAtkqz0k.png', u'Наше любимое кино': logobase + 'LSR5M6VxB0YDwv6803zrGFkq7vGQ3J.png', + u'НАШЕ НОВОЕ КИНО': logobase + 'y5jtghJCHG65ukyfjv 45stjxc76.png', u'Наше ТВ': logobase + 'O7Wx8kIB2aXEEmHLwBFIUhxn8B5WQF.png', + u'Наше ТВ': logobase + 'gTfxpSWtOGWBq7UHAHcz3XF10bzg78.png', u'Ника ТВ': logobase + 'pb3d3rBN4qW7ggzsosbAZXflfIv0Ty.png', u'НЛО ТВ': logobase + '2VGhYruaQo19G1NLGoOiTrwmPxef7d.png', - u'Новороссия ТВ': logobase + 'zUchDq13UVJRmlwAl3feV8cgKHYSyE.png', u'Новый канал': logobase + 'k7YdHhVpFZPIkBMXS2P2O2TkZSPf0y.png', u'Новый Христианский': logobase + 'gf6hTOcGXasvr47vTFRYZGV11xkDr5.png', u'Ностальгия': logobase + 'tIfiXoDaXoZevuGu9pZJSvX8unv1xl.png', @@ -427,14 +558,15 @@ u'НСТ': logobase + 'fKYzdlWRz68qd9mRZnWuxMY73EyaSz.png', u'НТВ': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', u'НТВ (+2)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', + u'НТВ (+2)': logobase + '20Ppq9Mmamklh3J27etMM89gFGYPYN.png', + u'НТВ (+3)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', + u'НТВ (+4)': logobase + 'FeYewxBbj2Cz8NOJDl98PTxYXI8cPg.png', + u'НТВ (16:9) ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'НТВ +7': logobase + 'f5N5GvTcqD5JLvdeRPha6LtkgUroht.png', u'НТВ HD': logobase + 'zdJ3ye6d3UWl5a56zm6LjqYH6ziSOs.png', u'НТВ Мир': logobase + 'ykpU49Gl1akVkgiVSsCm5B4TjXvQ64.png', - u'НТВ+ Кино плюс': logobase + 'cnz8ZMypP2HV6phwv3rkSVQ7CgJExi.png', - u'НТВ+ Киноклуб': logobase + 'nRnksgRuhojvbFDqh5KZ30XJQ4iyFO.png', - u'НТВ+ Кинохит': logobase + 'iQ0I0OnN11zzfQc5yivSJtXLiV0n8J.png', - u'НТВ+ Наше кино': logobase + 'UXJcZjVdZVIzciVHgGT3e6XxdRaBsD.png', - u'НТВ+ Наше новое кино': logobase + 'RcsEz5rIV3hQ6cwn6hqiwNAEOh1zNp.png', - u'НТВ+ Премьера': logobase + 'lDiI54Y3LjIAOg5VV0adicP3OJrdgo.png', + u'НТВ+ Баскетбол': logobase + 'bIWuyv7DJ65D5hIANkeo9SyIHGUXtn.png', + u'НТВ-ПЛЮС Инфоканал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'НТН (Украина)': logobase + 'LpQE1Odb1EoH5dJ90gWjItVyEYBXsw.png', u'ОНТ (Одесса)': logobase + 'P3tnfUDZ7f025rw6X7rFNT4aYmwMlJ.png', u'ОНТ Беларусь': logobase + 'a84If8XdqSFa6nHpegdujt52vAGNJW.png', @@ -442,20 +574,28 @@ u'Оплот ТВ': logobase + 'gvofGxTug45qSt1vsX0BPzQxGTrwTr.png', u'Оружие': logobase + 'CyDUCmYXK8WS2kXCX5kiAOFejnlwoP.png', u'Остросюжетное HD': logobase + 'mxF7CZsqsDRMMK4pN8ekdccEgvEsZC.png', + u'Открытый мир': logobase + 'VX8Nc6TW79bbns9IgJI6OdIj0r3DPG.png', u'ОТР': logobase + 'CqxKorK72v3ULbWkB3ZOhdte0duYZa.png', u'Охота и рыбалка': logobase + '5l2P20J6ebTh0ptOr27Hh704niP3nU.png', u'Охотник и рыболов': logobase + 'Ws2ddPI0b5Ie7PymoPUsboVlz9lYMS.png', u'Парк развлечений': logobase + 'beyfqyeacrFG0PrOeKUQhzQ4bV6Q5d.png', - u'Первый автомобильный (Украина)': logobase + 'oZTXrmNOxeJIVSbnuxqbiuAL3voXYa.png', u'Первый городской Одесса': logobase + 'vBOI3YTA4FDLD0c7BHHjq476p9GMCZ.png', u'Первый деловой': logobase + 'a1Qf3MpxC9FPD68Tj8vtUTNK8P25xr.png', + u'Первый деловой': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Первый канал': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (+2)': logobase + 'Lo2zy8h0msIieULsbHOjV1oSInHogr.png', + u'Первый канал (+2)': logobase + '7qA89ulmkQx43LT4XRkDiOepVnUBJ6.png', + u'Первый канал (+4)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (+4)': logobase + 'nHJycH0CkOhPeZ9DmB47iSMWP5HyWz.png', + u'Первый канал (+6)': logobase + '45vL1pa1DYHjYyMs1dtGC9RsACLmz3.png', + u'Первый канал (+8)': logobase + 'lRjN5HOOX0txLz8nigpKqpVw5CBKJZ.png', + u'Первый канал (4:3)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Первый канал (Евразия)': logobase + 'oPB8TcSFAuJtk4hBWgAqC9yNjMpfsi.png', u'Первый канал (Европа)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', + u'Первый канал (Молдова)': logobase + 'UsbyyrSaJmdjntY4muATwDytnseYYB.png', u'Первый канал (СНГ)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал HD': logobase + 'VxAFWzh1y88c8Aqa17TsxD2IO5pqoi.png', + u'Первый канал [16:9]': logobase + 'VGBjnqmDOxPOgDXK0seYUkmVloEpr2.png', u'Первый Метео': logobase + 'vdA7FKd1SCYhX4ovvzjQEHFdW3uA5X.png', u'Первый музыкальный HD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', u'Первый музыкальный UHD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', @@ -464,37 +604,63 @@ u'Первый музыкальный Россия HD': logobase + 'h7EDhdGypKmtfEP98O052SLlXUCcXt.png', u'Первый образовательный': logobase + '1kXxtStMuodaPU09H3rla3ry3QA2Wr.png', u'Первый Республиканский': logobase + 'Ubch3Ma8hsdSd8u6spOxVK5bZYsnDs.png', + u'Первый Ярославский': logobase + '6FZ4QqwM18DLS3WE6XKeF0rJzpSAIH.png', + u'Перец (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ПИК': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', u'ПИК HD': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', + u'ПИК HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Пиксель ТВ': logobase + 'BdCXB7wPZMNvlWzB5xEFzmsYUXcfXW.png', u'Планета': logobase + 'o7wnxC58C4KfCnX0hh0il0LTRlPfSg.png', u'Планета HD': logobase + '1QjirpCLi3q9qPu1CTvEvCB0BfINeo.png', u'ПлюсПлюс': logobase + '6gVIy7RMokFO61iVawgwbthe5mhgqm.png', + u'Правда ТУТ': logobase + '6JMI68m3c7uK5DtCvF9HcmCl0R6tJy.png', + u'Про Бизнес': logobase + 'ngUUkphyiFlnVUMIjoHzwrhcwDTsTt.png', + u'Про все': logobase + 'S2i01pmEOFJxwJtJCVF7PYc6Ij4jBX.png', + u'Продвижение ТВ': logobase + 'A13CkivHlwonag4N6pzFujtV00lwLL.png', u'Просвещение': logobase + 'Fpx3Vqqk2VNcXl4YjsfO53XscWadvF.png', u'Психология 21': logobase + 'AyLAdiqcKu5X8ykdLf2bO9HsxMlJdO.png', u'Пятница': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', + u'ПЯТНИЦА ( +4 )': logobase + 'HjLiq5t608j5kH6yjSiodCoFMUYwQL.png', u'Пятница (+2)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', + u'Пятница (+7)': logobase + '3UjE5kGjFxeXtseV0TlUTAHv71aQGO.png', u'Пятый канал': logobase + 'nIUDYY41OO4Xo0ntGpGv2rfpOR5ngt.png', + u'Пятый канал (+2)': logobase + 'rsEtkBk2ta1Wj1Y8uvxvSm4Vmbycir.png', + u'Пятый канал (+7)': logobase + 'q0XABDTYA29I5V6AMEqFQDdcr5Bj9k.png', + u'Пятый канал +4': logobase + 'jLZJgPYRXOQWt6vmNpisLyb0HC6wnT.png', u'Рада Украина': logobase + 'hBFJBYNiqZUom0ooVtNEJKliZwfioO.png', u'Радость моя': logobase + 'VRylZFYgFq7AL0FWcbf5JVOX3desn3.png', + u'Раз ТВ': logobase + 'CWaPSXdAL4Ejo1wNOqSWNPNVwbbhC7.png', u'РБК': logobase + 'JUMDXZxxB3UiVpMpU8t0aCpbVzxTmP.png', u'РЕН ТВ': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', + u'РЕН ТВ ( +4 )': logobase + 'n0OQ3o7skZJ0B9Htlj01o2VDeXKj0f.png', u'РЕН ТВ (+2)': logobase + 'WeHcyu6MG9GGpCmToRQnoL97iesKk9.png', + u'РЕН ТВ (+7)': logobase + 'bOa17Taj05Dr1k29vFt0hdbOqCeqGQ.png', + u'РЕН ТВ [16:9]': logobase + '8KwidLyxxFby0hoExGeUh4pbaetC16.png', + u'Ретро ТВ': logobase + 'nf4fvx9VYrz9U4cJZv2xTx09ejd1SE.png', u'РЖД ТВ': logobase + 'qa6177IUBWR0hYt1HKXxlMrF5MStOb.png', u'РИА Новости': logobase + 'DixgG6tVZzcVHO2LPQEx3QrtfoVah3.png', + u'Рим ТВ': logobase + '0bwbLdfI8rl5xpxQb3gy1NPxoGh2Ie.png', + u'РОДНОЕ КИНО': logobase + 'y7saqicb538iqo64ho46hh46.png', u'Россия 1': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', + u'РОССИЯ 1 ( +4 )': logobase + 'jy8HZ6Xf6hroTuXmjqSUgawX7O0g9t.png', u'Россия 1 (+2)': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', + u'Россия 1 (+2)': logobase + 'IVnok4EXJmqeIq8Kppprl456zRybNO.png', + u'Россия 1 (+6)': logobase + 'vWy8vur5mokNIzuCKPZpOnj59w8TP8.png', + u'Россия 1 (+8)': logobase + 'WY9uVth2S8kKBB5Jk7OVoXi9Omf02r.png', u'Россия 24': logobase + 'LWfGV6eICPYL7psaBfw2dOgGrOtHFS.png', u'Россия HD': logobase + 'ghvqmVpPWqn9x6POAm9UJBvXFzTrqN.png', u'Россия К': logobase + 'W9pWrec1BOJTmj8okrFeyM44wcpyd4.png', + u'Россия К': logobase + 'M0fzdXnwAvdXUdIMtyWGoGmCq2BTTa.png', u'Россия К (+2)': logobase + 'rVrDeCUhT4RPKIa2h9qRtnDon3Pd9X.png', u'Россия РТР (Украина)': logobase + '5o9OWeEw90hM5ouECuTLwj5QP8MwU3.png', + u'РТР Планета': logobase + 'wJMTJDEEwIhJWhNL0c6kfs5HVVJOVx.png', u'Русская комедия': logobase + '4Q3vGcJh5o0cgJB18scb90ogvL6OYV.png', u'Русская ночь': logobase + '9Sh9bJuj6js5AJsypAd6UvwnsIB25R.png', u'Русский бестселлер': logobase + 'b5JXaosgmcanh9EVJg52yBefvdLQF7.png', u'Русский детектив': logobase + '7I7VjbsFMIkZdoSbHFXiKEVZKNUbOM.png', u'Русский иллюзион': logobase + 'E9Imfr8aHN5midPVpNhJ3fo49FHbQE.png', u'Русский роман': logobase + '2smriIFxtj7Ojh4jyZq0K1XrT98XjS.png', + u'Русский роман HD': logobase + '2smriIFxtj7Ojh4jyZq0K1XrT98XjS.png', u'Русский экстрим': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Русский экстрим HD': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Рыжий': logobase + 'wfBSy60qHaPSKPpTfrNv9Q167iHIPu.png', @@ -502,6 +668,7 @@ u'Сарафан ТВ': logobase + 'LsYzwEOUspoxkY2hrTSy9zKqvpWlY8.png', u'Семейное HD': logobase + 'ORgdUno4IvNQYiShiEY4srF16JguLs.png', u'Сказка HD': logobase + 'dxhsesilX4JjwoCc8Qab4SYe8fxm75.png', + u'Смайл-ТВ': logobase + 'AFH2ud3ZiC5BXXPoq9OVbhCQTS0yOF.png', u'Совершенно секретно': logobase + 'BZJQEpa6Y4KL9tQjPHAIxbodw0KAyN.png', u'Сонце': logobase + 'TJXJVeoBFRMFrUgzpPW4dunJL6XSzn.png', u'Союз': logobase + 'YpsuBorUwulPHW3nI8O6nKETnEVB83.png', @@ -513,59 +680,86 @@ u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', u'СТС (+2)': logobase + 'FWNPupxL8N0STWKYEryyiV5sDFN2tS.png', + u'СТС (+2)': logobase + 'CPz3EbrU8SxT3CFj4JXtsx5YBJX3DH.png', + u'СТС (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'СТС Love': logobase + 'iciJHbEmJ1hHXAMhzC9cRWhmh9gH0L.png', + u'СТС Love (+7)': logobase + 'HVMQnlAIMnDToeJqFdxKojHUGA0QT1.png', u'ТА-Одесса': logobase + 'xnCANqGy0KUMtzCttmO9jyZUsXlEEI.png', + u'Тагил ТВ': logobase + 'ubegTi3hkdx7xtZCdOz8K0gBQLlght.png', u'ТБН': logobase + 'r9O7HmwQbFR4oKMH9yKAogE8xBzwz4.png', + u'ТБН': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ТВ 3': logobase + '427.png', + u'ТВ 3 (+2)': logobase + '427.png', u'ТВ 3 (+3)': logobase + '427.png', + u'ТВ3 ( +4 )': logobase + '7A2g2KEIG6GRcHWjGYCz73jymxAZP1.png', u'Тверской проспект': logobase + 'k6RPWpDIwfsOZ5qkqHF5ZdOf5GqfmH.png', u'ТВЦ': logobase + 'QEpQTskZ9hcfI0rgD8osHVYSv58pde.png', + u'ТВЦ (+2)': logobase + '7zGrgQeXL61DZu4RBOo3KDwnlOjymw.png', u'ТВЦ (+4)': logobase + 'dR7hMBOIq0MDGMkydFuksHGLNIWz7U.png', + u'ТВЦ (+4)': logobase + 'TJaQ1WxxrKacqXlGR4f7rWwfHAJYol.png', + u'ТВЦ (+7)': logobase + 'dlUvjPT15oxOQ2OD8lYegSkN37mQnJ.png', u'ТДК': logobase + 'eSrHE6Gws4U6JxhFXA3mQ4iDVc0SwS.png', u'Театр': logobase + '8qawrcOtzHZTAa3kI0rcDfVFEZoI5u.png', u'Теледом HD': logobase + 'XviuCfRo0T4WFTOhFaC978AwZ1a3Ge.png', u'Телекафе': logobase + 'fYRFV5oY197jXcyModfWVs0AlrCOIs.png', + u'Телемикс ТВ': logobase + 'FtmRS2h59h41VyWFPh4JtjOGHVjZAB.png', u'Телепутешествия': logobase + 'fz4bqwLySJAQkUN7l2EPKNqyvilfRD.png', u'ТЕТ': logobase + 'jp0YxRwXOyMWgVfDAyQaXNwle90sV3.png', u'Техно 24': logobase + 'JbUGHLuuZa3WQbjtbzUo0cDZkGnLRK.png', u'Тиса-1': logobase + 'pPRRy0SjPl3JKXdcZrQrdXQz3NTKOH.png', + u'Тлум HD': logobase + 'ZoOHbzwrnWPUaBLOWZJIHPyG5ssYfc.png', u'ТНВ-Планета': logobase + 'vBMv8AtIpDhBGQLjPoxkko3baWMFac.png', u'ТНВ-Татарстан': logobase + 'kMdYm3qFLgK52EV0ymvRBB43peSrj9.png', u'ТНТ': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', + u'ТНТ (+1) [Скат]': logobase + 'F5G2d7xGXnFfRMHSDCFiXWAdu1Oe2z.png', u'ТНТ (+2)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', + u'ТНТ (+3)': logobase + 'XIqPuPa3zUzZFevriI0urGganhT8Hm.png', u'ТНТ (+4)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', - u'ТНТ (+7)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', + u'ТНТ (+7)': logobase + '3nipGCZNvlMHrtgLyraU5YEA6sj0ek.png', u'ТНТ International (Европа)': logobase + 'd4loXdWqOPiwF7thzyBXX8JSspfVjU.png', u'ТНТ-Music': logobase + '6Go23tY9hpakfrvTEUH7Z7o5Y9hpOG.png', u'ТНТ4': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', + u'ТНТ4 ( +4 )': logobase + '14QPJWtD2gaXxRFVr2v9YxkvwDiT0O.png', u'ТНТ4 (+2)': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', u'ТНТ4 (+3)': logobase + 'dg9BEAtJr4IthpXjxV8NGAiu95NjXl.png', + u'ТНТ4 (+7)': logobase + 'QMacW5XucDi08h4stQ3AGt69IYAr97.png', u'Тонус ТВ': logobase + 'bE8WfReOerYTIbqPOo6VD2ajrFdOBT.png', u'Точка ТВ': logobase + 'JWwPbPnkWooIpKd5WYsdpfO3Mh14oA.png', + u'Третий цифровой (Одесса)': logobase + 'UERKEoCARXOG4CveFzUNnJoM9eOSwh.png', u'ТРК Киев': logobase + 'qW0p5z3De7COmSxTmvJ4ZA2wOuSJjg.png', u'ТРК Украина': logobase + '0co3dwhFDhoCVeTbfMV8ASYFYxSrWM.png', u'ТРО Союза': logobase + 'xAXy9iMyJ4wa2wmugJvbZuDIzc9pVz.png', u'Трофей': logobase + 'tQTWwjNBC8aLLWiWZfCj43BhWNH51I.png', + u'Трофей': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Трофей HD': logobase + 'cPi9B0icZpuvSRQiH0Kk6rNY8r1X65.png', u'ТТС': logobase + 'crsSIipA6N288sjn4EvUyOyTd0ed9A.png', u'Улыбка ребенка': logobase + 'P8aPFN50uJWJHkrqFGb7wgzfaTHUOO.png', u'Унiан': logobase + 'fhpFrTDoI9xx7UlK65KAjAbdTGehLL.png', u'Усадьба': logobase + '5yIxLQzQyZnH5EJcwpSGb28QuRTSFH.png', + u'Успех': logobase + 'RLcfsouYRxTNrQT97AOPIYfSneJyB6.png', + u'УТР': logobase + '83AcB7NfDhEh4tEcJx7d5Lm1l6dzpB.png', u'Феникс+ Кино': logobase + 'idiNkkBsxLwxWCF2VZrc9LQEevKh0d.png', u'Футбол': logobase + '472.png', u'Футбол 1 (Украина)': logobase + 'AMKtYwcgSAX5mTcPdhQDe4he18Jz7S.png', u'Футбол 1 HD': logobase + 'hZaWPKLVxTqUWZk0LTmLi1K1WUzX85.png', u'Футбол 2 (Украина)': logobase + 'PUXTI9mKcs49JnEENkh95KoKqt9VNg.png', u'Футбол 2 HD': logobase + 'TTvGrBoRM07MHY4q6bSwfzVuDKEGTi.png', + u'Хабар 24': logobase + '5ucUtr617J0k3od3cVRn1mMy0Ezi1x.png', + u'Хорошее кино': logobase + 'xEF8TOIGFqtgbOLkMM3TlA20Blel32.png', + u'Царьград ТВ': logobase + '9DJiua5LxhwBe2Is3l4LDJIH8zf5EE.png', u'Центральный канал': logobase + 'vzfLS14qVT0rSphoNeEuO2WDvXFoub.png', u'ЧГТРК Грозный': logobase + 'LqDNdQj6nf4MraZztT6ZnACn7yOJpV.png', u'Че': logobase + 'Hv36ZG48lg1mm2wdxAo3ju1EFS41Ga.png', u'Че (+2)': logobase + 'N7R86wgoPSELhZ9jCeqaykayjU8mbB.png', + u'Черноморская телекомпания': logobase + 'LJVoPVcJAE0s4DNmXCga0UPZkRkLTq.png', u'ЧП-Инфо': logobase + 'Xy7mLl3exaBKuLlDGdrRls6hyR7mSw.png', u'Шансон ТВ': logobase + 'VY0TyCCkKOj5b8BhBJjT020sQoxL9F.png', u'Эгоист ТВ': logobase + 'moG8uExVh4nw3MN7dmGFdysJHBWLk6.png', + u'Эко-ТВ': logobase + 'EmsE2NuqzHi5NXh6OIMMXQpfmD0VIl.png', + u'Эра ТВ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Ю': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', u'Ю (+2)': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', + u'Ювелирочка': logobase + 'pvKHFUCv25R51hJoUpAJfvjGnWyqoZ.png', u'Юмор ТВ': logobase + '6VFA1SVxeFHUsGaKPbNxWZREDkGeZw.png', u'Ямал Регион': logobase + 'xapccCaMjlT6JEAkZmk27wzCXlEU2m.png' -} \ No newline at end of file +} From a44f68ba4c293558bef8fd039504eb572b2e0908 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 21 Dec 2016 18:28:39 +0200 Subject: [PATCH 70/95] Applied changes for Allfon.tv playlist --- plugins/allfon_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py index eacedc0..fd9bb65 100755 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -54,7 +54,7 @@ def handle(self, connection, headers_only=False): # Match playlist with regexp - matches = re.finditer(r'\#EXTINF\:0\,ALLFON\.ORG (?P\S.+)\n.+\n.+\n(?P^acestream.+$)', + matches = re.finditer(r'\#EXTINF\:0\,(?P\S.+)\n.+\n.+\n(?P^acestream.+$)', Allfon.playlist, re.MULTILINE) add_ts = False From eeaa02a7810c2dfb8f911a118dc69386cb4bd79d Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Sat, 24 Dec 2016 11:21:59 +0200 Subject: [PATCH 71/95] Add options for audio transcoding for HLS --- aceconfig.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/aceconfig.py b/aceconfig.py index ac55b88..659ac1b 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -144,7 +144,19 @@ class AceConfig(acedefconfig.AceDefConfig): # transcodecmd['mp2'] = 'ffmpeg -i - -c:a mp2 -c:v mpeg2video -f mpegts -qscale:v 2 -'.split() # transcodecmd['mkv'] = 'ffmpeg -i - -c:a copy -c:v copy -f matroska -'.split() # transcodecmd['default'] = 'ffmpeg -i - -c:a copy -c:v copy -f mpegts -'.split() - + # ---------------------------------------------------- + # Transcoding configuration for HLS + # ---------------------------------------------------- + # If you use acestream engine ver >= 3.1.5 and vlcuse=True + # proxy automaticaly switch to HLS (HTTP Live Streaming) instead of HTTP Progressive Download + # You can use this settings for audio transcoding. This option applies only for Live-stream + # --------------------------------------------------- + # Transcode All audio to AAC + transcode_audio = 0 + # Transcode MP3 (use only when transcode_audio=1) + transcode_mp3 = 0 + # Transcode only AC3 to AAC (use only when transcode_audio=0) + transcode_ac3 = 0 # ---------------------------------------------------- videodelay = 0 # Obey PAUSE and RESUME commands from Engine From 1c98d280f35cb76d06a50e0d1750d4516700f387 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Sat, 24 Dec 2016 11:25:21 +0200 Subject: [PATCH 72/95] Add options for audio transcoding for HLS --- aceclient/aceclient.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/aceclient/aceclient.py b/aceclient/aceclient.py index 69155c6..69c8463 100644 --- a/aceclient/aceclient.py +++ b/aceclient/aceclient.py @@ -171,7 +171,13 @@ def START(self, datatype, value): ''' Start video method ''' - stream_type = 'output_format=http' if self._engine_version_code >= 3010500 and not AceConfig.vlcuse else '' + if self._engine_version_code >= 3010500 and AceConfig.vlcuse: + stream_type = 'output_format=hls' + ' transcode_audio=' + str(AceConfig.transcode_audio) \ + + ' transcode_mp3=' + str(AceConfig.transcode_mp3) \ + + ' transcode_ac3=' + str(AceConfig.transcode_ac3) + else: + stream_type = 'output_format=http' + self._urlresult = AsyncResult() self._write(AceMessage.request.START(datatype.upper(), value, stream_type)) self._getResult() From 291d258491638c3e5107fe649b1cbf5ed7a661ea Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 28 Dec 2016 21:55:28 +0200 Subject: [PATCH 73/95] Allfon TV changes --- plugins/allfon_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py index fd9bb65..27d679d 100755 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -57,6 +57,7 @@ def handle(self, connection, headers_only=False): matches = re.finditer(r'\#EXTINF\:0\,(?P\S.+)\n.+\n.+\n(?P^acestream.+$)', Allfon.playlist, re.MULTILINE) + add_ts = False try: if connection.splittedpath[2].lower() == 'ts': From a84be1f68ca41f66d161842e8bea914f56130319 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Fri, 30 Dec 2016 09:35:03 +0200 Subject: [PATCH 74/95] Fixed VLC broadcasting --- vlcclient/vlcclient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vlcclient/vlcclient.py b/vlcclient/vlcclient.py index a084dc9..f49f460 100644 --- a/vlcclient/vlcclient.py +++ b/vlcclient/vlcclient.py @@ -4,7 +4,7 @@ import gevent import gevent.event -import gevent.coros +import gevent.lock import telnetlib import logging from vlcmessages import * @@ -40,7 +40,7 @@ def __init__( # Authentication done event self._auth = gevent.event.AsyncResult() # Request lock - self._resultlock = gevent.coros.RLock() + self._resultlock = gevent.lock.RLock() # Request result self._result = gevent.event.AsyncResult() # VLC version string From be70c1b3baca79644817a2d74d0b94d535d3246a Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Sun, 15 Jan 2017 11:45:30 +0200 Subject: [PATCH 75/95] Add gzip compression for playlist url --- plugins/torrenttv_plugin.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index da0b0a7..d8b0161 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -11,6 +11,8 @@ import urlparse import md5 import traceback +import gzip +from StringIO import StringIO from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator import config.torrenttv as config @@ -32,7 +34,7 @@ def __init__(self, AceConfig, AceStuff): self.etag = None self.logomap = config.logomap self.updatelogos = p2pconfig.email != 're.place@me' and p2pconfig.password != 'ReplaceMe' - + if config.updateevery: gevent.spawn(self.playlistTimedDownloader) @@ -46,27 +48,45 @@ def downloadPlaylist(self): try: self.logger.debug('Trying to download playlist') req = urllib2.Request(config.url, headers={'User-Agent' : "Magic Browser"}) - origin = urllib2.urlopen(req, timeout=10).read() + req.add_header('Accept-encoding' , 'gzip') + response = urllib2.urlopen(req, timeout=15) + + origin = '' + + if response.info().get('Content-Encoding') == 'gzip': + # read the encoded response into a buffer + buffer = StringIO(response.read()) + # gzip decode the response + f = gzip.GzipFile(fileobj=buffer) + # store the result + origin = f.read() + # close the buffer + buffer.close() + # else if the response isn't gzip-encoded + self.logger.debug('Playlist downloaded using gzip compression') + else: + # store the result + origin = response.read() + self.logger.debug('Playlist downloaded') + matches = re.finditer(r',(?P\S.+) \((?P.+)\)\n(?P^.+$)', origin, re.MULTILINE) self.playlisttime = int(time.time()) self.playlist = PlaylistGenerator() self.channels = dict() m = md5.new() - + for match in matches: itemdict = match.groupdict() encname = itemdict.get('name') name = encname.decode('UTF-8') logo = config.logomap.get(name) url = itemdict['url'] - if logo: itemdict['logo'] = logo if (url.startswith('acestream://')) or (url.startswith('http://') and url.endswith('.acelive')): self.channels[name] = url itemdict['url'] = urllib2.quote(encname, '') + '.mp4' - self.playlist.addItem(itemdict) m.update(encname) @@ -140,7 +160,7 @@ def handle(self, connection, headers_only=False): hostport = connection.headers['Host'] path = '' if len(self.channels) == 0 else '/torrenttv/channel' add_ts = True if path.endswith('/ts') else False - header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' % (config.tvgurl, config.tvgshift) + header = '#EXTM3U url-tvg="%s" tvg-shift=%d deinterlace=auto\n' % (config.tvgurl, config.tvgshift) exported = self.playlist.exportm3u(hostport, path, add_ts=add_ts, header=header, fmt=fmt) connection.send_response(200) From 569de632a89ad2fee78667c3f9a57a10215c77c5 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Sun, 15 Jan 2017 20:44:31 +0200 Subject: [PATCH 76/95] universalization EOL (EndOFLine) for playlist --- plugins/torrenttv_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index d8b0161..4efb53e 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -68,8 +68,8 @@ def downloadPlaylist(self): # store the result origin = response.read() self.logger.debug('Playlist downloaded') - - matches = re.finditer(r',(?P\S.+) \((?P.+)\)\n(?P^.+$)', origin, re.MULTILINE) + + matches = re.finditer(r',(?P\S.+) \((?P.+)\)[\r\n]+(?P[^\r\n]+)?', origin, re.MULTILINE) self.playlisttime = int(time.time()) self.playlist = PlaylistGenerator() self.channels = dict() From c90d5d316355a01b23802440355623ec5080fd60 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Sun, 22 Jan 2017 14:58:24 +0200 Subject: [PATCH 77/95] Add subclass for override the log messages Change log massages in Cyrillic to readable format Logger output changes --- acehttp.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/acehttp.py b/acehttp.py index 2ed67ca..520b4d5 100755 --- a/acehttp.py +++ b/acehttp.py @@ -29,6 +29,7 @@ import time import threading import urllib2 +import urllib import urlparse import Queue import aceclient @@ -51,6 +52,19 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): requestlist = [] + def log_message(self, format, *args): + logger.debug("%s - - [%s] %s\n" % + (self.address_string(), + self.log_date_time_string(), + urllib.unquote(format%args).decode('UTF-8'))) + + def log_request(self, code='-', size='-'): + logger.debug('"%s" %s %s', + self.requestline, str(code), str(size)) + + def log_error(self, format, *args): + logger.debug(format, *args) + def handle_one_request(self): ''' Add request to requestlist, handle request and remove from the list @@ -190,7 +204,7 @@ def do_GET(self, headers_only=False): self.dieWithError(403) # 403 Forbidden return - logger.info("Accepted connection from " + self.clientip + " path " + self.path) + logger.info("Accepted connection from " + self.clientip + " path " + urllib.unquote(self.path).decode('UTF-8')) try: self.splittedpath = self.path.split('/') From 902226e26d5118c43c37252d4ef04b3f11e5cdd6 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Sun, 22 Jan 2017 22:04:16 +0200 Subject: [PATCH 78/95] Small changes for loggin --- acehttp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acehttp.py b/acehttp.py index 520b4d5..999293b 100755 --- a/acehttp.py +++ b/acehttp.py @@ -53,7 +53,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): requestlist = [] def log_message(self, format, *args): - logger.debug("%s - - [%s] %s\n" % + logger.info("%s - - [%s] %s\n" % (self.address_string(), self.log_date_time_string(), urllib.unquote(format%args).decode('UTF-8'))) @@ -63,7 +63,7 @@ def log_request(self, code='-', size='-'): self.requestline, str(code), str(size)) def log_error(self, format, *args): - logger.debug(format, *args) + logger.error(format, *args) def handle_one_request(self): ''' From 4f71a186d910fa87d6dbaf9861bd29ddeab9ed8b Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Mon, 23 Jan 2017 19:37:05 +0200 Subject: [PATCH 79/95] Get client IP from Header --- acehttp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acehttp.py b/acehttp.py index 999293b..8914490 100755 --- a/acehttp.py +++ b/acehttp.py @@ -190,7 +190,8 @@ def do_GET(self, headers_only=False): # Current greenlet self.requestgreenlet = gevent.getcurrent() # Connected client IP address - self.clientip = self.request.getpeername()[0] + self.clientip = self.headers['X-Forwarded-For'] \ + if self.headers.has_key('X-Forwarded-For') else self.request.getpeername()[0] if AceConfig.firewall: # If firewall enabled From 61ff44388fa1163184abe475ea0b1777241b95ce Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Tue, 24 Jan 2017 02:05:02 +0200 Subject: [PATCH 80/95] Add locale support for stat info --- plugins/stat_plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index 4cbbd77..e6b05ab 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -5,10 +5,12 @@ ''' from modules.PluginInterface import AceProxyPlugin import time +import locale class Stat(AceProxyPlugin): handlers = ('stat', 'favicon.ico') - + locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) + def __init__(self, AceConfig, AceStuff): self.config = AceConfig self.stuff = AceStuff @@ -39,5 +41,5 @@ def handle(self, connection, headers_only=False): else: connection.wfile.write(i) connection.wfile.write('' + c.handler.clientip + '') - connection.wfile.write('' + time.strftime('%d %b %Y %H:%M:%S', time.localtime(c.connectionTime)) + '') + connection.wfile.write('' + time.strftime('%c', time.localtime(c.connectionTime)) + '') connection.wfile.write('') From 14006d7fae51968bf13fd63ef0539ea92318f436 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Thu, 2 Feb 2017 09:23:44 +0200 Subject: [PATCH 81/95] Add duration time & GeoIP info for connections --- plugins/stat_plugin.py | 44 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index e6b05ab..aec03c0 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -5,17 +5,43 @@ ''' from modules.PluginInterface import AceProxyPlugin import time -import locale +import json +import plugins.modules.ipaddr as ipaddr +from urllib import urlopen + +localnetranges = ( + '192.168.0.0/16', + '10.0.0.0/8', + '172.16.0.0/12', + '224.0.0.0/4', + '240.0.0.0/5', + '127.0.0.0/8', + ) class Stat(AceProxyPlugin): handlers = ('stat', 'favicon.ico') - locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) - + def __init__(self, AceConfig, AceStuff): self.config = AceConfig self.stuff = AceStuff + def geo_ip_lookup(self, ip_address): + """ + Look up the geo information based on the IP address passed in + """ + GEOIP_LOOKUP_URL = 'http://api.2ip.ua/geo.json?ip=%s' + lookup_url = GEOIP_LOOKUP_URL % ip_address + response = json.loads(urlopen(lookup_url).read()) + + return {'country_code' : response['country_code'], + 'country' : response['country'], + 'region' : response['region'], + 'city' : response['city'], + } + def handle(self, connection, headers_only=False): + current_time = time.time() + if connection.reqtype == 'favicon.ico': connection.send_response(404) return @@ -28,6 +54,7 @@ def handle(self, connection, headers_only=False): return connection.wfile.write( + '' '

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') connection.wfile.write( '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') @@ -40,6 +67,15 @@ def handle(self, connection, headers_only=False): connection.wfile.write(c.channelName.encode('UTF8')) else: connection.wfile.write(i) + connection.wfile.write('') - connection.wfile.write('') + clientinrange = any(map(lambda i: ipaddr.IPAddress(c.handler.clientip) in ipaddr.IPNetwork(i),localnetranges)) + + if clientinrange: + connection.wfile.write('') + else: + connection.wfile.write('') + + connection.wfile.write('') + connection.wfile.write('') connection.wfile.write('
' + c.handler.clientip + '' + time.strftime('%c', time.localtime(c.connectionTime)) + '
' + 'Local IP adress' + '' + self.geo_ip_lookup(c.handler.clientip).get('country') + ' , ' + self.geo_ip_lookup(c.handler.clientip).get('city') + '' + time.strftime('%c', time.localtime(c.connectionTime)) + '' + time.strftime("%H:%M:%S", time.gmtime(current_time-c.connectionTime)) + '
') From 73b886f6103b2af1293781aed0b834a2ba3dc502 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Mon, 6 Feb 2017 13:35:48 +0200 Subject: [PATCH 82/95] Minor changes for stat_plugin stability --- plugins/stat_plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index aec03c0..4a4310e 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -31,7 +31,7 @@ def geo_ip_lookup(self, ip_address): """ GEOIP_LOOKUP_URL = 'http://api.2ip.ua/geo.json?ip=%s' lookup_url = GEOIP_LOOKUP_URL % ip_address - response = json.loads(urlopen(lookup_url).read()) + response = json.loads(urlopen(lookup_url, timeout=5).read()) return {'country_code' : response['country_code'], 'country' : response['country'], @@ -74,7 +74,8 @@ def handle(self, connection, headers_only=False): if clientinrange: connection.wfile.write('' + 'Local IP adress' + '') else: - connection.wfile.write('' + self.geo_ip_lookup(c.handler.clientip).get('country') + ' , ' + self.geo_ip_lookup(c.handler.clientip).get('city') + '') + geo_ip_info = self.geo_ip_lookup(c.handler.clientip) + connection.wfile.write('' + geo_ip_info.get('country') + ', ' + geo_ip_info.get('city') + '') connection.wfile.write('' + time.strftime('%c', time.localtime(c.connectionTime)) + '') connection.wfile.write('' + time.strftime("%H:%M:%S", time.gmtime(current_time-c.connectionTime)) + '') From 01d5b271a9f634f274a4a5fe21da298caa25a118 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 8 Feb 2017 19:15:21 +0200 Subject: [PATCH 83/95] Change urllib to urllib2 for geoip --- plugins/stat_plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index 4a4310e..b038faf 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -7,7 +7,7 @@ import time import json import plugins.modules.ipaddr as ipaddr -from urllib import urlopen +import urllib2 localnetranges = ( '192.168.0.0/16', @@ -31,8 +31,9 @@ def geo_ip_lookup(self, ip_address): """ GEOIP_LOOKUP_URL = 'http://api.2ip.ua/geo.json?ip=%s' lookup_url = GEOIP_LOOKUP_URL % ip_address - response = json.loads(urlopen(lookup_url, timeout=5).read()) - + req = urllib2.Request(lookup_url, headers={'User-Agent' : "Magic Browser"}) + response = json.loads(urllib2.urlopen(req, timeout=5).read()) + return {'country_code' : response['country_code'], 'country' : response['country'], 'region' : response['region'], From ee28f14cb50a54eb4934ec0292c66287c584d764 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Sat, 11 Feb 2017 18:34:05 +0200 Subject: [PATCH 84/95] Update Picon's logobase --- plugins/config/torrenttv.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 28088a9..75aa37c 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -26,15 +26,18 @@ u'1 HD': logobase + 'FtLnmUwjG18XJFEKYvLKjwUq1gwHVZ.png', u'1 Мистический': logobase + '6NZHK1Lz0SbqVMhy20L9gPEJjW89mQ.png', u'1+1': logobase + 'omm2Xc8xSVIT6Od6ca4QqMrEXw3jaK.png', + u'10 канал (Израиль)': logobase + 'bkVtuqhnGlL4Ykxlha255ief1YRIDJ.png', u'100% News': logobase + '9yEWvPmTcFS8lyQ5NjJ7vbYOa3bx1W.png', u'112 Украина': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'112 Украина HD': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'2+2': logobase + 'XHXBC3ghvhh100BNXylSgJLx5FVQgD.png', + u'22 канал (Израиль)': logobase + '6VuqNvDh7nyHsxPolqLr7YpfYLkTNW.png', u'24 TV': logobase + 'QkclX0M9kXSGGDRhBw8H0T37VDzwNb.png', u'24 Док': logobase + 'H1UXBai10DjYfScfv1sNAILV9EPDer.png', u'24 Украина': logobase + 'XfKEdfsy4S1zbE8n2tB1tNNe9IkrRP.png', u'2x2': logobase + 'hTpJUV15GSTxZ5kJQGLcn42kCzKyEH.png', u'2x2 (+2)': logobase + 'ZPOhZle6vrDaulo2KMmyrCkkkLn7Ci.png', + u'31 канал': logobase + '69d16xSeYrnw4OAO0cSWY9stdLp5cd.png', u'360 Tune Box': logobase + 'WofrwD5WhK8TxQvbTBchuo1QKcbbFS.png', u'360 градусов': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', u'360 градусов HD': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', @@ -49,7 +52,10 @@ u'8 телеканал ( Красноярск )': logobase + 'MmesR31Atj0JdU6q1MaqXXdm533h00.png', u'9 волна': logobase + '8zDeTSBhsmJDbXo9dxHA1c9mDOP9sP.png', u'9 канал Израиль': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', + u'9 канал HD (Израиль)': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', u'A-One UA': logobase + '8rEKNCXhKeWnUvGhWCsrb2RN5FMqJi.png', + u'ABC TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Akudji HD': logobase + 'V7sRTl5PvWvFg45engRn1p6Zb7rB1e.png', u'Al Jazeera English': logobase + 'B1AC3jl0CY8u4qxO0aIEHVMFQFvBUu.png', u'AMC': logobase + 'rozrC8ZPYA1YGajyow35doeVcaQzpa.png', u'Amedia 1': logobase + 'jT3vAEOG5jTd2t8GcC797Bw5W0kSl9.png', @@ -463,22 +469,29 @@ u'Карусель International': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'КВН ТВ': logobase + 'xb1VGuwpJFlQ9fWExWngUeSUSC2xJW.png', u'Киевская Русь': logobase + 'C1AZimW2NnNA17H1uJLxxePUMTPQZ7.png', + u'Кино HD': logobase + '5mvhuskUDmyIjUM9WDzZ5u24SeDqmw.png', + u'Кино HD (резерв)': logobase + 'tJiazXmwL8E8jxdZZDCTCyCuD6iit6.png', u'Кино 2': logobase + '4liS5ZApeFUGXcGL6gFrbw7swx2ZaB.png', u'Кино ТВ': logobase + 'KkITMDICqC1erWdSqyOqoccqde2wHC.png', - u'КИНОКОМЕДИЯ': logobase + 'y087hci8ityixcnyxinxoafhiu.png', - u'КИНОМИКС': logobase + 'y6b876ih8g7R876hfug3897wrhj.png', + u'Кинозал (Боевики)': logobase + 'w9lyoCqdUiL9m5aD6VWg9M6iZ188fd.png', + u'Кинозал (Детский)': logobase + 'wFeLlGKHXw5b0hOT3sTFLtxE9qs9Om.png', + u'Кинозал (Комедия)': logobase + 'cos0nJtFYR3FIqZrkNj7ZyJTj5uo6M.png', + u'Кинозал (Фантастика)': logobase + 'oEMp3C5ys20s6vmtvkJ1YQNf2GwqYn.png', + u'Кинокомедия': logobase + 'y087hci8ityixcnyxinxoafhiu.png', + u'Киномикс': logobase + 'y6b876ih8g7R876hfug3897wrhj.png', u'Кинопоказ': logobase + 'v0JEbxExcFI8dVEzCkpZUoktgiS9t7.png', u'Кинопоказ 1 HD': logobase + 'pNA1vR3sPoovYNKzO4TU6NQcSXNqjk.png', u'Кинопоказ 2 HD': logobase + 'Yf2XKrtorOF2wSZ23Q9NHhL2MfOcqi.png', u'КиноПремиум HD': logobase + 'p580CRZ8bBS6dw3plMWhhxXSzQ59uS.png', - u'КИНОПРЕМЬЕРА HD': logobase + 'y4Ihg8o7ikgJHF768iJHFH76iu.png', - u'КИНОСВИДАНИЕ': logobase + 'y1876iuyf7645urjg56utfy.png', - u'КИНОСЕМЬЯ': logobase + 'y2oerfuhdj2108nbs4ev875esk47dn.png', - u'КИНОХИТ': logobase + 'y3igbjZ&LGyqDWGIASUY78AWID.png', - u'Конный Мир': logobase + 'd8gVne1Em14rIfM6pSsiuufXZeYQqi.png', - u'Кот ТВ': logobase + 'Rtdtc632nbBQ2TsymgXhkOt7nhytjW.png', + u'Кинопремьера HD': logobase + 'y4Ihg8o7ikgJHF768iJHFH76iu.png', + u'Киносвидание': logobase + 'y1876iuyf7645urjg56utfy.png', + u'Киносемья': logobase + 'y2oerfuhdj2108nbs4ev875esk47dn.png', + u'Киносерия': logobase + '4TMYdVpZYXafyIumuB5d7PrjFnslyT.png', + u'Кинохит': logobase + 'y3igbjZ&LGyqDWGIASUY78AWID.png', + u'Конный мир': logobase + 'd8gVne1Em14rIfM6pSsiuufXZeYQqi.png', + u'Конный мир HD': logobase + 'q87VlmfaBeBhq8SOhAu0SVPEGjiqWM.png', u'Красная линия': logobase + 'I43S6jd5noclar0LlPJnyY8adonmUV.png', - u'КРИК-ТВ': logobase + 'z4Iv85kqJXWHQJ5475DNxOxlo9Ty5v.png', + u'Крик-ТВ': logobase + 'z4Iv85kqJXWHQJ5475DNxOxlo9Ty5v.png', u'Круг': logobase + 'fbzJYZiRscBiEvaWEFmzvYzEHdaNN7.png', u'Крым 1 HD': logobase + 'fVpBXMtyVEJ538kDDcWsezJbgcG8zA.png', u'Крым 24': logobase + 'x4s9QQXR8KerO13nlkuEujzcv5ww5F.png', From 46c7b7f60c0f141fb204282f2a5fb8e7b3de37a0 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Mon, 20 Feb 2017 08:19:14 +0200 Subject: [PATCH 85/95] Add locale & jsoncheck for geoip response --- plugins/stat_plugin.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index b038faf..e709ada 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -5,9 +5,11 @@ ''' from modules.PluginInterface import AceProxyPlugin import time -import json -import plugins.modules.ipaddr as ipaddr +import logging import urllib2 +import plugins.modules.ipaddr as ipaddr +import locale +import json localnetranges = ( '192.168.0.0/16', @@ -20,25 +22,30 @@ class Stat(AceProxyPlugin): handlers = ('stat', 'favicon.ico') + locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) + logger = logging.getLogger('STAT') def __init__(self, AceConfig, AceStuff): self.config = AceConfig self.stuff = AceStuff def geo_ip_lookup(self, ip_address): - """ - Look up the geo information based on the IP address passed in - """ - GEOIP_LOOKUP_URL = 'http://api.2ip.ua/geo.json?ip=%s' - lookup_url = GEOIP_LOOKUP_URL % ip_address + lookup_url = 'http://api.2ip.ua/geo.json?ip=' + ip_address + Stat.logger.debug('Trying to obtain geoip info : ' + lookup_url) + req = urllib2.Request(lookup_url, headers={'User-Agent' : "Magic Browser"}) - response = json.loads(urllib2.urlopen(req, timeout=5).read()) - - return {'country_code' : response['country_code'], - 'country' : response['country'], - 'region' : response['region'], - 'city' : response['city'], - } + response = self._jsoncheck(json.loads(urllib2.urlopen(req, timeout=10).read())) + + return {'country' : response['country'], + 'city' : response['city']} + + def _jsoncheck(self, jsonresult): + country = jsonresult['country'] + city = jsonresult['city'] + + if (country == '-' or not country) or (city == '-' or not city): + jsonresult = {"country":"---","city":"---"} + return jsonresult def handle(self, connection, headers_only=False): current_time = time.time() @@ -76,7 +83,7 @@ def handle(self, connection, headers_only=False): connection.wfile.write('' + 'Local IP adress' + '') else: geo_ip_info = self.geo_ip_lookup(c.handler.clientip) - connection.wfile.write('' + geo_ip_info.get('country') + ', ' + geo_ip_info.get('city') + '') + connection.wfile.write('' + geo_ip_info.get('country').encode('utf-8') + ', ' + geo_ip_info.get('city').encode('utf-8') + '') connection.wfile.write('' + time.strftime('%c', time.localtime(c.connectionTime)) + '') connection.wfile.write('' + time.strftime("%H:%M:%S", time.gmtime(current_time-c.connectionTime)) + '') From 7f6570fcefa78948df12669d4ebe1e05f20df94f Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Tue, 21 Feb 2017 08:56:18 +0200 Subject: [PATCH 86/95] Add table header & change GeoIP API for stability --- plugins/stat_plugin.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index e709ada..0d084da 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -30,22 +30,15 @@ def __init__(self, AceConfig, AceStuff): self.stuff = AceStuff def geo_ip_lookup(self, ip_address): - lookup_url = 'http://api.2ip.ua/geo.json?ip=' + ip_address +# lookup_url = 'http://api.2ip.ua/geo.json?ip=' + ip_address + lookup_url = 'http://freegeoip.net/json/'+ip_address Stat.logger.debug('Trying to obtain geoip info : ' + lookup_url) req = urllib2.Request(lookup_url, headers={'User-Agent' : "Magic Browser"}) - response = self._jsoncheck(json.loads(urllib2.urlopen(req, timeout=10).read())) + response = json.loads(urllib2.urlopen(req, timeout=10).read()) - return {'country' : response['country'], - 'city' : response['city']} - - def _jsoncheck(self, jsonresult): - country = jsonresult['country'] - city = jsonresult['city'] - - if (country == '-' or not country) or (city == '-' or not city): - jsonresult = {"country":"---","city":"---"} - return jsonresult + return {'country' : '-/-' if not response['country_name'] else response['country_name'] , + 'city' : '-/-' if not response['city'] else response['city']} def handle(self, connection, headers_only=False): current_time = time.time() @@ -65,7 +58,9 @@ def handle(self, connection, headers_only=False): '' '

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') connection.wfile.write( - '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') + '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') + connection.wfile.write( + '') for i in self.stuff.clientcounter.clients: for c in self.stuff.clientcounter.clients[i]: connection.wfile.write('') clientinrange = any(map(lambda i: ipaddr.IPAddress(c.handler.clientip) in ipaddr.IPNetwork(i),localnetranges)) - if clientinrange: - connection.wfile.write('') + if clientinrange: + connection.wfile.write('') else: - geo_ip_info = self.geo_ip_lookup(c.handler.clientip) - connection.wfile.write('') + geo_ip_info = self.geo_ip_lookup(c.handler.clientip) + connection.wfile.write('') connection.wfile.write('') - connection.wfile.write('') + connection.wfile.write('') connection.wfile.write('
Channel nameClient IPLocationStart timeDuration
') @@ -79,12 +74,12 @@ def handle(self, connection, headers_only=False): connection.wfile.write('' + c.handler.clientip + '' + 'Local IP adress' + '' + 'Local IP adress' + '' + geo_ip_info.get('country').encode('utf-8') + ', ' + geo_ip_info.get('city').encode('utf-8') + '' + geo_ip_info.get('country').encode('utf-8') + ', ' + geo_ip_info.get('city').encode('utf-8') + '' + time.strftime('%c', time.localtime(c.connectionTime)) + '' + time.strftime("%H:%M:%S", time.gmtime(current_time-c.connectionTime)) + '
' + time.strftime("%H:%M:%S", time.gmtime(current_time-c.connectionTime)) + '
') From 913668e952d51c67fe02d56808010e0dcd9b8eb0 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Thu, 23 Feb 2017 21:24:24 +0200 Subject: [PATCH 87/95] Remove locale & add world-flags-sprite css --- plugins/stat_plugin.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index 0d084da..74e7c27 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -8,7 +8,6 @@ import logging import urllib2 import plugins.modules.ipaddr as ipaddr -import locale import json localnetranges = ( @@ -22,7 +21,6 @@ class Stat(AceProxyPlugin): handlers = ('stat', 'favicon.ico') - locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) logger = logging.getLogger('STAT') def __init__(self, AceConfig, AceStuff): @@ -30,15 +28,15 @@ def __init__(self, AceConfig, AceStuff): self.stuff = AceStuff def geo_ip_lookup(self, ip_address): -# lookup_url = 'http://api.2ip.ua/geo.json?ip=' + ip_address - lookup_url = 'http://freegeoip.net/json/'+ip_address + lookup_url = 'http://freegeoip.net/json/' + ip_address Stat.logger.debug('Trying to obtain geoip info : ' + lookup_url) req = urllib2.Request(lookup_url, headers={'User-Agent' : "Magic Browser"}) response = json.loads(urllib2.urlopen(req, timeout=10).read()) - return {'country' : '-/-' if not response['country_name'] else response['country_name'] , - 'city' : '-/-' if not response['city'] else response['city']} + return {'country_code' : '' if not response['country_code'] else response['country_code'] , + 'country' : '' if not response['country_name'] else response['country_name'] , + 'city' : '' if not response['city'] else response['city']} def handle(self, connection, headers_only=False): current_time = time.time() @@ -54,13 +52,16 @@ def handle(self, connection, headers_only=False): if headers_only: return - connection.wfile.write( - '' - '

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') - connection.wfile.write( - '
Concurrent connections limit: ' + str(self.config.maxconns) + '
') - connection.wfile.write( - '') + connection.wfile.write('') + connection.wfile.write('') + connection.wfile.write('AceProxy stat info') + connection.wfile.write('') + connection.wfile.write('') + connection.wfile.write('') + connection.wfile.write('

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') + connection.wfile.write('
Concurrent connections limit: ' + str(self.config.maxconns) + '
Channel nameClient IPLocationStart timeDuration
') + connection.wfile.write('') + for i in self.stuff.clientcounter.clients: for c in self.stuff.clientcounter.clients[i]: connection.wfile.write('') + connection.wfile.write('') else: geo_ip_info = self.geo_ip_lookup(c.handler.clientip) - connection.wfile.write('') - + connection.wfile.write('') connection.wfile.write('') connection.wfile.write('') - connection.wfile.write('
Channel nameClient IPLocationStart timeDuration
') @@ -75,11 +76,10 @@ def handle(self, connection, headers_only=False): clientinrange = any(map(lambda i: ipaddr.IPAddress(c.handler.clientip) in ipaddr.IPNetwork(i),localnetranges)) if clientinrange: - connection.wfile.write('' + 'Local IP adress' + '' + 'Local IP adress ' + '' + geo_ip_info.get('country').encode('utf-8') + ', ' + geo_ip_info.get('city').encode('utf-8') + '' + geo_ip_info.get('country').encode('UTF8') + ', ' + geo_ip_info.get('city').encode('UTF8') + ' ' + '' + time.strftime('%c', time.localtime(c.connectionTime)) + '' + time.strftime("%H:%M:%S", time.gmtime(current_time-c.connectionTime)) + '
') + connection.wfile.write('') From c5641d44983904768b5ece967f780bda68d1d468 Mon Sep 17 00:00:00 2001 From: zixelmike Date: Thu, 16 Mar 2017 22:05:58 +0300 Subject: [PATCH 88/95] Update torrenttv.py --- plugins/config/torrenttv.py | 176 ++++++++++++++++++++++++++++++------ 1 file changed, 149 insertions(+), 27 deletions(-) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 75aa37c..749bcc4 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -23,6 +23,7 @@ logobase = 'http://torrent-tv.ru/uploads/' logomap = { u'0x0 Fireplace HD': logobase + 'H1VboxDJC7sE7x3nKXoYT0X5r4LIqD.png', + u'0x0 Music HD': logobase + 'hFj4tnC5uqAgpod3doHnJGZxgZXaiP.png', u'1 HD': logobase + 'FtLnmUwjG18XJFEKYvLKjwUq1gwHVZ.png', u'1 Мистический': logobase + '6NZHK1Lz0SbqVMhy20L9gPEJjW89mQ.png', u'1+1': logobase + 'omm2Xc8xSVIT6Od6ca4QqMrEXw3jaK.png', @@ -30,6 +31,7 @@ u'100% News': logobase + '9yEWvPmTcFS8lyQ5NjJ7vbYOa3bx1W.png', u'112 Украина': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'112 Украина HD': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', + u'12 Канал (Омск)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'2+2': logobase + 'XHXBC3ghvhh100BNXylSgJLx5FVQgD.png', u'22 канал (Израиль)': logobase + '6VuqNvDh7nyHsxPolqLr7YpfYLkTNW.png', u'24 TV': logobase + 'QkclX0M9kXSGGDRhBw8H0T37VDzwNb.png', @@ -37,6 +39,7 @@ u'24 Украина': logobase + 'XfKEdfsy4S1zbE8n2tB1tNNe9IkrRP.png', u'2x2': logobase + 'hTpJUV15GSTxZ5kJQGLcn42kCzKyEH.png', u'2x2 (+2)': logobase + 'ZPOhZle6vrDaulo2KMmyrCkkkLn7Ci.png', + u'2x2 (+5)': logobase + 'WMlvhNmhzmk2JgKct8Oa5FcWHmR7fZ.png', u'31 канал': logobase + '69d16xSeYrnw4OAO0cSWY9stdLp5cd.png', u'360 Tune Box': logobase + 'WofrwD5WhK8TxQvbTBchuo1QKcbbFS.png', u'360 градусов': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', @@ -45,13 +48,13 @@ u'3S TV': logobase + 'AL7uQOgsyYqE7V1BagxC2jqQVOvucp.png', u'43 Канал HD': logobase + '1mNoU1QXmcmK48eVTlnADtsEBmA2sN.png', u'47 канал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'49 канал ( Новосибирск )': logobase + 'WK3XEVPHqpV3VkKEBHi8QSXoPKPj6T.png', + u'49 канал (Новосибирск)': logobase + 'WK3XEVPHqpV3VkKEBHi8QSXoPKPj6T.png', u'5 канал (Украина)': logobase + '9La0uS6S8rMKr0BOh6vSCQLNiCqN7N.png', u'8 канал': logobase + 'wKPUJjcpZIfI5zBSZwNayOlv63zRNV.png', u'8 канал (+3)': logobase + 'XaNnxrpV00f3Zcfyw6WAg6uMT8NznG.png', - u'8 телеканал ( Красноярск )': logobase + 'MmesR31Atj0JdU6q1MaqXXdm533h00.png', + u'8 канал (Красноярск)': logobase + 'wKPUJjcpZIfI5zBSZwNayOlv63zRNV.png', u'9 волна': logobase + '8zDeTSBhsmJDbXo9dxHA1c9mDOP9sP.png', - u'9 канал Израиль': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', + u'9 канал (Израиль)': logobase + 'DLEdAyl9qChDL6eDpI5HhVaArJZQHd.png', u'9 канал HD (Израиль)': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', u'A-One UA': logobase + '8rEKNCXhKeWnUvGhWCsrb2RN5FMqJi.png', u'ABC TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', @@ -59,6 +62,7 @@ u'Al Jazeera English': logobase + 'B1AC3jl0CY8u4qxO0aIEHVMFQFvBUu.png', u'AMC': logobase + 'rozrC8ZPYA1YGajyow35doeVcaQzpa.png', u'Amedia 1': logobase + 'jT3vAEOG5jTd2t8GcC797Bw5W0kSl9.png', + u'Amedia 1 HD': logobase + 'KPN6ULwGzTNbKziTI9sN7xfFPhtRzc.png', u'Amedia 2': logobase + 'fAvxTQbWu0DAcMkqej0m73KohAcQJw.png', u'Amedia Hit': logobase + '3lKypp7zXsJ3FDusXzKw9hHnVLythX.png', u'Amedia Premium': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', @@ -76,6 +80,7 @@ u'Az TV': logobase + 'FB2qxgNm8xTU6YiR2fY1jH4PIRR2SF.png', u'B4U Music UK': logobase + 'LlfDsq3FEU2D4P3lmj25fvl6zQnvNM.png', u'BBC World News': logobase + '8cSYWwvq6BAzcGDjJODvCGQYGJ2c4S.png', + u'Beatz TV HD': logobase + 'kXJ08AkdOdRbaYON4Tsmkgk14zqifk.png', u'Bloomberg': logobase + 'OTsoNZRT8xjXz5nnTICPCQRvNLjBal.png', u'Blue Hustler': logobase + 'dFDAUzpGhQFOyHz59iyi7gsGHHMQbj.png', u'Bollywood HD': logobase + 'HXzWxtMxmXkrpgr88Kh8Z1A8F6o5dK.png', @@ -85,11 +90,15 @@ u'Brazzers TV Europe': logobase + 'OmVBp3Kz4Lx722dq2e1OxE26QSxrDA.png', u'Bridge HD': logobase + 'hSVhfaLPt58KBflQjQxwiDqqO6h3rm.png', u'Bridge TV': logobase + 'dPhBiaViIznwjeWU3pTbIXFmS3iJkU.png', + u'Bridge TV Classic': logobase + 'DsJRpcbI6rgjONbQftC5nHt1XMAXYQ.png', + u'Bridge TV Dance': logobase + 'ofmgiZlRmwOZtTaYtUtfErZS3ODDDM.png', u'Brodilo TV HD': logobase + '0pLPWiGqZjDe2O4vbPd8QL0qGsA9lq.png', u'BT Sport 1': logobase + 'whrGkbmFh1pcdxuanV5u4zcD7PDuhd.png', u'Business': logobase + 'wvwXc2x8Bjev90GD6LO6t7TL2aKW1h.png', u'C More Tennis': logobase + 'ql4qy7gHN4GtWhcqfd97HqXUs8HXI9.png', u'Canal 3': logobase + 'rb39jMz38xLuXKGCuWD2GcHHMEzfX1.png', + u'Candy': logobase + 'YW8BJnImniuR7l1U85Khw31mX2XO0S.png', + u'Candy HD': logobase + 'YW8BJnImniuR7l1U85Khw31mX2XO0S.png', u'Canal+ Sport HD (France)': logobase + 'I4vGNtOsYtkBnwOwFi94RHazK1ngqw.png', u'Cartoon Network': logobase + 'NTNQLLri3Hh9iqYjW7VEkFYJsTLjk9.png', u'CBC': logobase + 'GuGWslWHNQ2fb88q6JYKSAV1n9A6hF.png', @@ -99,6 +108,9 @@ u'CCTV News': logobase + 'TyWVFvKwV3lOYYXWVJSj8HMu5AodJr.png', u'CCTV Русский': logobase + 'DcaE92lKRSDFBPlYUnsNRauFLbotKu.png', u'CentoXCento TV': logobase + 'dPuPlqJtkLMRy7NmOhyHDfL0PJqYj4.png', + u'CGTN': logobase + 'TyWVFvKwV3lOYYXWVJSj8HMu5AodJr.png', + u'CGTN Documentary': logobase + 'dqoArqO8dd09vzsSSQPqLUj5wM9O9M.png', + u'CGTN Русский': logobase + 'DcaE92lKRSDFBPlYUnsNRauFLbotKu.png', u'CNL': logobase + 'wwQ6u4lFXyaUDJLu2JOh6mtbkIz0Nf.png', u'CNN International': logobase + 'lIwbRbM3ve4ixhFXp75Oap9nzcO71G.png', u'Da Vinci Learning': logobase + 'Yl6p1IDDkZxxiUa3p2JxI66mIlOPns.png', @@ -113,9 +125,10 @@ u'Discovery HD Showcase': logobase + 'HCjNT859EyclaIl0GSueEiUacnR5Qd.png', u'Discovery Science': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', u'Discovery Science HD': logobase + 'nnC8c8Z3iWbGSsgDORtWrS6O5n0ljr.png', + u'Discovery Showcase HD': logobase + 'CWSMBfG9Cjz73B4r9MJWMCsstIBZcY.png', u'Disney Channel': logobase + 'JxEjTeXwExjnxutQGKJBmMI85tpNqK.png', u'Disney Channel (+7)': logobase + 'LZQKp6qoXQ8Ef64qUzcCOACy8qJAoq.png', - u'Disney Channel +2': logobase + 'awk1MeZ3IP1HOAtPt4jfvdR2P6YqIc.png', + u'Disney Channel (+2)': logobase + 'awk1MeZ3IP1HOAtPt4jfvdR2P6YqIc.png', u'Djing Animation HD': logobase + 'ZA8pwcclRpzTflNKLqeZmDHCA7lHoX.png', u'Djing Classic HD': logobase + 'fDCLK7k2nYRKPAD125JdHM5uuEM7bZ.png', u'Djing Ibiza HD': logobase + 'aGqPHXL6MxB8j4GXldadnOR3r5D03f.png', @@ -135,6 +148,7 @@ u'Eska Rock TV HD': logobase + 'VU2POxFhYCM4XhFAtOp8Y4sdlbu32M.png', u'Eska TV': logobase + '1KX19DgurgoaSKNTTpjXCeSQr1epwv.png', u'Eska Wawa TV HD': logobase + 'r9d697qox4MFgypnVqeILYWKcfbwNc.png', + u'ETV pluss': logobase + 'cZBJJjYDyuuJHMQk3ZOogcDZYcu2SK.png', u'Espreso TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ETV pluss (Эстония)': logobase + 'cZBJJjYDyuuJHMQk3ZOogcDZYcu2SK.png', u'EU Music': logobase + 'hubnM8JT96ngUDKI6WBYv9aKMIyWvS.png', @@ -146,11 +160,13 @@ u'Eurosport 1 HD': logobase + 'DpFTzUEA3y67Z6ObTPF4xH0XLNRAZm.png', u'Eurosport 2': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', u'Eurosport 2 HD': logobase + '0mo5WOW3xn7FmcIRM9PMZxb2Zgad86.png', - u'EWTN UK & Ireland': logobase + 'maP6bdwOGv77xHPvRnKBHww9cmG2oV.png', + u'Eurosport News': logobase + 'fwZZMuQvqp6eMlji1jhFuEn3v85CsJ.png', + u'EWTN UK and Ireland': logobase + 'maP6bdwOGv77xHPvRnKBHww9cmG2oV.png', u'Exotica TV': logobase + 'K5hm3mURkkDaSc7RI5RDti5edynMGl.png', u'Extreme Sports': logobase + '21FhIqWK82JDPNuLTEIC9hSO2EHfks.png', u'FAP TV': logobase + 'd5ulbooZicKw3aMLwn2Ho0yD9c46T2.png', u'FAP TV 2': logobase + 'YVmUBY8cBPO8IoL4djlyeKCDbs6f0p.png', + u'FAP TV 2 HD': logobase + 'KqVXT8GPTPuPEM87hZSqDwajqcBBgY.png', u'FAP TV 3': logobase + 'S5gXPPcF53lFHQ3kgJofKMQudRQKPt.png', u'FAP TV 4': logobase + 'xv1QyBV2OkaDbZ1ebRB2FWwfw40BR5.png', u'FAP TV Amateur': logobase + 'LGfemDTp3Z3mvhn1aKSGVvtlNu5zMF.png', @@ -163,11 +179,13 @@ u'FAP TV Older': logobase + 'G1L5Qh2QVnSu6zEVua6KOI4hxtUfcn.png', u'FAP TV Parody': logobase + 'mu4NIjgq7Xu5nt32zTSxGvBOfk90xt.png', u'FAP TV Pissing': logobase + '3QJ9sSAI90ALizvl1B2sbt8WxIVLSu.png', + u'FAP TV Shemale': logobase + 'd5ulbooZicKw3aMLwn2Ho0yD9c46T2.png', u'FAP TV Teaching': logobase + '3MzvfwDLSX8zUmmuO7TpS8O2iD03Tj.png', u'FAP TV Teens': logobase + '7Z9gyzosm1oUC82aFfA3mBG9YAjOrS.png', u'FAP TV Trans': logobase + '05P2Z8lWWIgaEmvWcTRO1R9F0lMPZL.png', u'Fashion One HD': logobase + 'iPs2ptiBXm8h0KSnRmyqu45texHNig.png', u'Fashion TV': logobase + 'PSjqabjhYIBqcS8hUA8WNrZEjV4zZY.png', + u'Fashion TV HD': logobase + 'z6q34MCsDCntK8jbJXp4pBQiPlIiNx.png', u'FashionBox': logobase + 'bNAHBFlVPUSr7PI5bPQVXQcJiOcjd2.png', u'Fast and FunBox HD': logobase + 'D88mJMen7WRxrg0C7TFFeiXXI1gRHM.png', u'FightBox HD': logobase + 'mZbmQtneRV9cWJsMQ2PSfkGqbsJm9o.png', @@ -181,8 +199,10 @@ u'French Lover TV': logobase + 'XHZQcVegBeqVMOYDqzr9EruPgbQpX0.png', u'Fоx HD': logobase + 'Pl8S60EJ52htHxi1gAw1SS1y8i1p3z.png', u'Fоx Life HD': logobase + 'Vou521VpOGAGqhp4HUfiG7BSbKNSk6.png', + u'GakkuTV': logobase + 'Xf4EuvwQ5YhlZGy1ueGCzZvY6ZW36k.png', u'Galaxy TV': logobase + 'pU2NsRP9CVtEgQuDTi9jcTYV8iAD4a.png', u'Gamanoid': logobase + 'DxEQkipHmJjSY0wHGKHDV6eAZKiwoG.png', + u'Gamanoid HD': logobase + 'DxEQkipHmJjSY0wHGKHDV6eAZKiwoG.png', u'Game Show': logobase + 'Uc9sBHj0DAYzfXMZmdxeriCBZvUpeb.png', u'Game Show': logobase + 'WgebxxZr1mTHsGtMTYZzLvV3OnPQrQ.png', u'Game Show-HD': logobase + 'sB7MetJbNWRBt2Hk56gffI5F9hraLt.png', @@ -201,9 +221,12 @@ u'Hello TV HD': logobase + 'o4WXPUTr5f2tuXFdjuLqgEg3qieGre.png', u'History Channel': logobase + '9cVifexiWW0qWDhhpnLNVydoZkeRqZ.png', u'History Channel HD': logobase + 'VFgU260pmiIyPxCzD3f8R7Yc6DXClH.png', + u'HiT Music Channel': logobase + 'n7tghbIGAAh8cyWe7Li0E2iNEJSqkl.png', u'Hit TV': logobase + 'FnCnW1vmvm8gjAwMGXe2Q8qtN1C3zy.png', + u'Hit TV (Казахстан)': logobase + 'jTaWMihq6ZttbxHba4BweWDLywOHpi.png', u'Hustler HD': logobase + 'LBz8ia8AASewVuLjMs5v4MDiVYfsJO.png', u'Hustler TV': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', + u'Ictimai TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'iConcerts HD': logobase + 'fLNDK6Nxz7xMv61nOsZvED749DlOtz.png', u'Ictimai': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ICTV': logobase + 'YuNtYxhj9vqgTU9kVuz9imhqviY4PZ.png', @@ -215,6 +238,9 @@ u'Jahonnamo': logobase + 'OqNuJdvRTdBh5NeydnpmCfMnTJs9XZ.png', u'Jasmin TV': logobase + 'MBRUFcRx5wtHLZmgahvcGdXB2ERdst.png', u'JimJam': logobase + 'BPDFCK5SQF3mXu5MsDNSdtvz4Gjawo.png', + u'Kazakh TV': logobase + 'il4jEk1Mrw3Miub4nIjzSFNwq7TkaI.png', + u'Kazsport': logobase + 'Hrl1mL3UFqhv8OE7z0wbo4Z0zR6Knc.png', + u'Kral TV': logobase + 'pojhh0tCHuXtk5KkqUl5Nd3JUD3gBB.png', u'Juce TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Kidzone': logobase + 'CMwzzYODhlLDC188nnF28qxfwvMkWa.png', u'Kino Space TV HD': logobase + '28A3JkU9kVHQWimTbBinKpqYyjZHcA.png', @@ -222,13 +248,13 @@ u'Kvartal TV': logobase + 'HeyS7MiMm71GffX6GeOFDMIs2NOgLv.png', u'Kvartal TV': logobase + 'zkg5FwPBQonqxQlFIOblrboFQpHK6i.png', u'Lale': logobase + '99AZ5VwFy9yZrgsdZ6kIxcoYqwSyUH.png', - u'Lale': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Lider TV': logobase + 'LByLhdeQ30Ln5pSZRYSpoLpXQS5Ytr.png', u'Life78': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', u'Life78 HD': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', u'LifeNews': logobase + 'K8dBCUNz1BSwbgUYYU04i2qJpQKLMc.png', u'LifeNews HD': logobase + 'Mvurp6cp7Sq2fV3tnFBwPtJy7Ifm1i.png', u'LTV World': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Luxe TV HD': logobase + 'gggiBSZ0905Dkcxtdj8Mh8ie14kpPa.png', u'Luxury World': logobase + 'QvaV83TwgWHnaGmw4C822MCNYi9zio.png', u'Luxury World': logobase + 'LB9Pgt6QNYOVDEJqKI2xcmq6VZOP0m.png', u'Lviv TV': logobase + 'VMl4S9yd5IYFAVFaR8XG6KEm5pgVu4.png', @@ -245,12 +271,15 @@ u'MTV Live HD': logobase + 'WjyYXtYHhG5COxGab7luHb1bmvAioA.png', u'MTV Rocks': logobase + 'SEwn7rL2FxPcf5Ol9KRKedIwXlpsAP.png', u'MTV Россия': logobase + 'P5ijp2sRQsKVZkOJwjdqIVgYdJzhpZ.png', + u'MTV Россия HD': logobase + 'YOE9yEg2KwmItjHwMHcQDVvIMsfOTg.png', u'Music Box RU': logobase + 'zaHCW7nPCyGRnqHDkenCIXo7d6vR7v.png', u'Music Box TV': logobase + 'fvt4pris0lwnVhSyUrh8QlyzWBhbgz.png', u'Music Box UA': logobase + '8T7Rnct8q2VHhRm0BcGFxCjxuYEArc.png', u'Music TV': logobase + 'XQB0y9UMs7XT4YTtKd8AcYigdhP3Wv.png', u'Mute TV 3D': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', u'Mute TV HD': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', + u'Muzzone': logobase + 'eZTTUdsMLYJwwO5ewPcE9mpAmdmSXj.png', + u'My Zen TV HD': logobase + 'GkgzM41JkGOhbS33KepRPoAckZv2Xz.png', u'MUTV': logobase + 'yDbkEj466sOzY5cCPVbCW6NIT7kkly.png', u'N4 TV': logobase + 'R1NDSrUKnB1RoT8iv1okSe2xY39W4J.png', u'Nashe Music HD': logobase + 'Xv91mHlQ4d5fCWu7xxpgy847qTWyc2.png', @@ -260,19 +289,26 @@ u'National Geographic HD': logobase + 'hK1waimMq9eAp0ugM19moSoQvUeve5.png', u'NBA HD': logobase + 'ZfUh18TUU7KVCgzPPi79D5Zqtu8njC.png', u'News One': logobase + 'SnaQqBa1YMS2b8b8EGdZSMCConLbDS.png', + u'News One HD': logobase + 'SnaQqBa1YMS2b8b8EGdZSMCConLbDS.png', + u'NewsNetwork': logobase + 'puoOIFeHu3ojMldKVG7VVObJji589p.png', u'NHK World TV': logobase + 'JJ8Sh9c7zA3PaXlK1ZaNjy3GgWQFh2.png', u'Nick Jr.': logobase + 'D87kJ3fIWIm5wKi5qxm24nbuPQv0U8.png', u'Nickelodeon': logobase + 'j66xpaZbfiYIgQxv76QAPckPVjmLNs.png', u'Nickelodeon HD': logobase + 'CFYx7Bd1aDSgkqjMLLQjFE6xe3u1E0.png', + u'NRJ Hits HD': logobase + 'lSwvcnY5XfGEwyrKSvygH73fcYDZKI.png', u'Nuart TV': logobase + 'aqMIuUixqLQYmJITPnOGtFkRPuTqKa.png', u'O-la-la': logobase + '7ddvb8Tivq7yuEgffrKildHi2BQcCE.png', u'O-TV': logobase + 'UlIt4rE3FZs7IQmPN3tsGS6fqY5F1D.png', u'Ocean-TV': logobase + 'cvBAngU16nJU1bEzxAEcMPiPvf7fVT.png', + u'Olympic Channel HD': logobase + '3rHDtuBvfq5GWQbPhW7hDEcYeabTwO.png', + u'ON TV': logobase + 'W4wsmGmpUkVVAhcBocGkPsRA0o1xTb.png', u'OSN Movies Action HD': logobase + 'BUJiIaGHNzhRASjqNAgRIoC8Xz2Ket.png', u'OSN Movies HD': logobase + 'UMzAVPfEGT4NxDvqA2nZrv61IFh4Bs.png', u'Outdoor Channel': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', u'Outdoor HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Outdoor Channel HD': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', u'PanArmenian': logobase + 'Goi0hXa9CeThenpkvfPq7E961TMUAT.png', + u'PanArmenian TV': logobase + 'tPxLstRFhFYfGzT8Elg0a1QGGf7wf4.png', u'Paramount Channel': logobase + 'Iyzjhb5BBRKfvEZdBwNJ785jzroODU.png', u'PARAMOUNT CHANNEL HD': logobase + 'LD6lUAlPHwEUOGeqDfa4xBfCdZY8nC.png', u'Paramount Comedy': logobase + '5EtvAWXB7VK1Yw82yvO28sY28dU4ZC.png', @@ -283,16 +319,18 @@ u'Pink O TV': logobase + 'aI99kH6bHY5Qt2ph4W1Nh0wxdzd1p8.png', u'Playboy TV': logobase + 'lIWBxmt5GDl9tg4KsQNtA0CuZWdOHH.png', u'Polonia 1': logobase + '3fzUyCBgUoJ6zqc92KSXh9g9utJEuO.png', + u'Power Turk HD': logobase + '6xL4Vdrzqi4TIuFeum4nnpju6tRguQ.png', u'Pro Все': logobase + 'OavKhOpBMn27qPDKJXXm5rgATgovgn.png', u'QTV': logobase + 'zmh1xStjBZGJ6U5g3xLoemD2oT3w3h.png', u'R1': logobase + 'zI5CoA2qyiJYuXzqsXo0LBmJv9Tern.png', u'Rai 1': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Rai 2': logobase + '9m3iFYefQC0919T6EfaIhIjiINBuEk.png', - u'RaiNews 24': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Rai News 24': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Real Madrid TV': logobase + 'KfoLG7goywcRhMMCc3sO8IVwhuATLp.png', u'Redlight HD': logobase + '5tFZcJtKAZRbXtKGDuLe8FZ3lK9LI5.png', u'Renome (Одесса)': logobase + 'uXenrBCtSwAsv31RtOAXdn3hXm3i5N.png', u'Retro Music TV': logobase + 'zVS9G1oine56udlAK30gNut16iJ9Ft.png', + u'Revelation TV': logobase + 'gK8K5dIwNW8YqrGZCilO42UX0NF03i.png', u'RMC TV': logobase + '3CiAgachWg7ohgoU1Gilcm73hXhT41.png', u'RTG HD': logobase + 'g4MDyw0yqXWkIar8eH0cgCz4xhiKON.png', u'RTG TV': logobase + 'IeaOjwR6Q9eJjGZr0LYk2tpchM3ITZ.png', @@ -303,8 +341,10 @@ u'Russia Today Arabic': logobase + 'OPbYfpQc4ShP8JmgPeiavUroY98H8L.png', u'Russia Today Doc': logobase + 'VOYx5PIhDPrWiZIcCYpm1xrDSQZnsN.png', u'Russia Today Doc HD': logobase + 'b8QePfFi6zsCDS7hfTeFWES5UN4SAk.png', + u'Russia Today Doc.': logobase + 'VOYx5PIhDPrWiZIcCYpm1xrDSQZnsN.png', u'Russia Today Espanol': logobase + 'zkPwjfFbktNO8EYDaHlYhwAeT88dmY.png', u'Russia Today HD': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', + u'Russian Music Box HD': logobase + 'CbPggS9SpKMrgFW5myDvhLEFvm0hwp.png', u'Rustavi2': logobase + 'WAgaH51sY7ujcWlPQGWeUCP12zIU8t.png', u'Rytas TV': logobase + 'TBousteH8Slv9wtLiQ0ssNHXslA4yl.png', u'Satisfaction HD': logobase + 'isdNgbfGENuaDPSMzsz8WMjBzc1rah.png', @@ -312,11 +352,13 @@ u'SET': logobase + 'tKzOKIcOQYrFl1VL8B0QqERFXCAYfU.png', u'SET HD': logobase + 'sX1unYoKj8JR7m8lbtkmPCClRrjAZ9.png', u'Setanta Sports + HD': logobase + 'jW7pJhmebW2fZsXUTvDuRQLahgiLlU.png', + u'Setanta Sports HD': logobase + 'MZ8syJ7LqKWUIqPJKiwkUvxHNJznW1.png', u'Setanta Sports Eurasia HD': logobase + '1wgHdJP76TCItF14FxDwBtak8tmxRv.png', u'Sextosenso': logobase + 'HXMvFMLO9weHUcolVmmMKpz98T0K4K.png', u'Shant TV': logobase + 'IgJpH1JzyjI55Ki2G2Ybz6jzOkwG0T.png', - u'Shop 24': logobase + 'bCuxLyvoTk8l5cBRSyKFXjWjYucvlu.png', + u'Shop24': logobase + 'bCuxLyvoTk8l5cBRSyKFXjWjYucvlu.png', u'Shopping Live': logobase + 'HVwxC489SYFr8Ttqs1he9RZjEmJjPn.png', + u'Shopping TV': logobase + 'Yhipu86vXMGf0hxCTXLezpGpcUbibW.png', u'SHOPping-TV (Ukraine)': logobase + 'Yhipu86vXMGf0hxCTXLezpGpcUbibW.png', u'Sky Sports 1': logobase + 'eFxo8gGgylCVPEv9IG4Pud4LdmfAhF.png', u'Sky Sports 1 HD': logobase + 'U4ngWpMf0MTbeOpX4Tgxelz0tPCBs8.png', @@ -341,7 +383,7 @@ u'TiJi': logobase + 'mD3GW0E7rdPwc4stjk7xrLI2gZn4Hq.png', u'TLC': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', u'TLC HD': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', - u'TMB RU ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'TMB RU': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Tonis': logobase + 'f4NBZiozbHDSxPGRpphIXhvlBjytkG.png', u'Tonis': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', u'Tonis HD': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', @@ -352,7 +394,7 @@ u'Travel + adventure HD': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', u'Travel Channel': logobase + 'fhmrYjlpC0YMxFd2RqOolbjlXMr0tI.png', u'Travel Channel HD': logobase + 'zfnAGLCvIu1fx9hfrITAZMoo9HYww4.png', - u'TRT Türk': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'TRT Turk': logobase + 'BDkqm302OLdoXCwpKFVUe8S43OlWj9.png', u'TSN 1': logobase + 'GJsJeixsOKvFaXz8XKqvX4Uh6sUicu.png', u'TV 1000': logobase + 'WJMEvVafVakrm7BUMy1lzku7VQCx25.png', u'TV 1000 Action East': logobase + 'GblbxkDGXZyW5oWt9W8wuERQAiZ7ZT.png', @@ -363,13 +405,19 @@ u'TV Safina': logobase + 'mJUmNhJbQqcr2NPppAryEJqDPBJGV0.png', u'TV Sale': logobase + 'hs0YdiUTlpRtb3wTiP4cXboX0H9oTN.png', u'TV XXI (TV21)': logobase + 'TKchoTWZFRMmGDBok08zoEFJ8mJJCe.png', + u'TV-5': logobase + 'iPHeoajUJttv5qZl2yfcOYB1qFx6nv.png', + u'TV1000 Comedy': logobase + 'Rg2qtCBJcy6nGCC0D3EjX4xl1Rn2kw.png', u'TV1000 Comedy HD': logobase + 'ygGiR2hkQLySH6khdo8GV9CyMJ8dXi.png', u'TV1000 Megahit HD': logobase + 'lVPY7WCjn1WM6NL6tfLFy8iGA4yk3Z.png', u'TV1000 Premium HD': logobase + 'raoDrpin8VKmi522LZWzSF0fLRO04m.png', + u'TV3 Catalunya': logobase + 'JdogLgSiKoXxC7ZlxVLogeOLMQUQyN.png', u'TV5 Monde Europe': logobase + 'ko7rbRBnyK1iINkLOA2adRvgVOEgUK.png', u'TVC 21': logobase + 't6oWXfHEIMAjJ9GSEChuI2JHQg7KzL.png', u'TVM Channel': logobase + 'aCAKhUCXdMfvBlPD4sHnmOe0LNpPB5.png', u'TVT 1': logobase + 'CKKdhDfmno9O52tMfWptiAQT0IBWV8.png', + u'UA:Крим': logobase + 'iN5vheceBnPmO18uXHGgKwq7q2PHtF.png', + u'UATV': logobase + '83AcB7NfDhEh4tEcJx7d5Lm1l6dzpB.png', + u'USArmenia TV': logobase + 'llpqN7S6EpCPdoYIHjle6gc4cXlk17.png', u'UA:Перший (Украина)': logobase + 'iN5vheceBnPmO18uXHGgKwq7q2PHtF.png', u'UBR': logobase + 'F6EzmjkOBVB0gmn1kQX6itv5VvFml5.png', u'VH1': logobase + '58.png', @@ -379,27 +427,36 @@ u'Viasat Fotboll HD': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', u'Viasat Golf': logobase + 'IGpQl5iTxaDEPyKffdDEpU0EU0SPiO.png', u'Viasat History': logobase + 'MWGbB8wJp5Gm4vbPHl0ktohDDjMKdr.png', + u'Viasat History HD (Европа)': logobase + '4HljxmfSEerSo6WANdtw0Phz2g9XL8.png', u'Viasat Hockey': logobase + 'CuAbCRGdf3Z1FGFiwErTbHZ3lAMJzr.png', u'Viasat Motor': logobase + 'RuYtGxEpqJ5DG7WxGCMWNDXosRdh59.png', u'Viasat Nature East': logobase + 'yimDcPvajJcUKQm9bY15cDdp3rJFcp.png', + u'Viasat Nature HD (Европа)': logobase + '6iBmDGCV7UArjU0ZkKnOZDcyB1FbYe.png', u'Viasat Nature-History HD': logobase + 'pSP6zxmuO4PU6xa6KRlZ9L8vvVM2Dy.png', u'Viasat Sport': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat Sport Baltic': logobase + 'ZIITckvF1w5u1MlubmhoG45HxPgcZZ.png', u'Viasat Sport HD': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat Sport Sverige': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', - u'Vintage TV': logobase + 'mhFKSRmQIsgPnIUCWFzWbMAXK5dn3i.png', + u'Vintage TV UK': logobase + 'mhFKSRmQIsgPnIUCWFzWbMAXK5dn3i.png', u'Virgin Radio TV': logobase + '6DgkEMl3HtkpKQbzVovPdSYhy9f3ne.png', u'Vision Norden': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Visit-X HD': logobase + 'PKLqXFmYlYpo1RbkynS7SfL79gH9fg.png', u'VOX DE': logobase + 'sfwtJrwyJw1vyIG83Ym08tm5pYIH2Q.png', u'WBC HD': logobase + 'fMdJvc8moH4HGH5OYqsZwskCowQYuE.png', u'World Fashion': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', + u'World Fashion HD': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', + u'XSport': logobase + 'SCwOf3movQkrJf7ez4tNLZYlWo1b7t.png', u'ZDF': logobase + '5SH5FeZiITw27CPxscjksZp272u7He.png', - u'ZIK Ukraine': logobase + 'dB1PmpYtgeI0C4u7Cv1QoXHlvc4JtT.png', + u'ZIK (Украина)': logobase + 'dB1PmpYtgeI0C4u7Cv1QoXHlvc4JtT.png', u'Zoom': logobase + 'SyisYhg411o7z9kXci4vfpLq4KBZZ4.png', u'Авто 24': logobase + 'GZwKgvkC0vfYquFAn1dKhppNxdDit9.png', u'Авто плюс': logobase + 'WkRxjy6fJEBJ5NZiaGn2j05eqfFfQq.png', + u'Алматы': logobase + 'PFh3dcBeiHjebw4axqaGQuBe7z5JXD.png', u'АРТ 24 (Одесса)': logobase + 'H7YvrLp7Zwdb0G4rmB99E5nF4TTmsf.png', u'Архыз24': logobase + 'tuMbRlnkeMiYQ6u9oeiAVfHF0F20RB.png', + u'Асыл Арна': logobase + 'A6i6ABMom6qyxURqSnbC1ls6bZ6wxO.png', + u'Бел Бизнес Канал': logobase + '5zagkrx7iqLQUxx2jVJM58vT68BP85.png', + u'Беларусь 1': logobase + 'S32pk5GPcXbTyyCllIjfpqPPkBAy4Q.png', u'Башкир ТВ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Беларусь 24': logobase + 'GxA1KJP5YwpWc38BoPEmLwQH6uDeEz.png', u'Беларусь 4': logobase + 'xTwlFgOsv50ls1Z9ecK0MhC0hCGX2W.png', @@ -407,9 +464,9 @@ u'Белсат ТВ': logobase + '9VYuUQxx1ss7ieu2upENtlibyamBP0.png', u'Бигуди': logobase + 'JvcMdB5e6KVBpbXT12ulzmDqenheRx.png', u'Бобер': logobase + '2Edln8vEbg7UUSVUo7lIJPR780OWAR.png', + u'Бокс ТВ': logobase + 's3qlrVrIjISf7K0G4HHRYw5rmAej4b.png', u'Брянская губерния': logobase + 'V5M08NU3jRbd2wKvtVLIRbg9J6ObAZ.png', u'БСТ': logobase + '05dZ8fzOmf1lXYue2OvVsQ21eIeX69.png', - u'БСТ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Винтаж ТВ': logobase + '6yngbEKfgy6XtMw28INQCoOdulf2tC.png', u'Вместе РФ': logobase + 'qa50GYekwBWym7KtoJdzrWHWqN8TeU.png', u'Возрождение ТВ': logobase + 'CKWaxJsLQYiJmnkLhtysIn8hb0NK8b.png', @@ -417,14 +474,20 @@ u'Вопросы и ответы': logobase + 'xbV8M35FkvpieQ3TUEL8fhwU8MzjmQ.png', u'Время': logobase + 'F44yKDJQLsX0llpZ2wupg8V5vHx5fF.png', u'ВТВ': logobase + 'svsUD6TinXyv3B1q5sZf3fI9ebmpaF.png', + u'Галичина': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Глазами туриста': logobase + '0tuGdZaluGh8jDJulzD0MOcHOkKMJI.png', + u'Глазами туриста HD': logobase + 'zNR9KYnBTV8erX7UP9L9C7Daco29mC.png', u'Глас': logobase + '6m1xUI7vFyadFMFxXeIZjlATt7rlWw.png', u'Горизонт': logobase + 'f6wqkzO4WZW7D5Z9xIB4VkMRGmgXUU.png', + u'Горизонт ТВ': logobase + 'o7wnxC58C4KfCnX0hh0il0LTRlPfSg.png', + u'Горящий камин HD': logobase + 'ujkL8d4MnAfrWSOBme8UxGFq6AJodT.png', u'Громадське ТБ HD': logobase + 'Ovkd9TiVv3nLcKPwQS2wkJ85KyYCMQ.png', u'Дача': logobase + 'mX1irJVUyLtrXr9vfDdhknRqiqV9wA.png', u'Детский': logobase + 'jk8kody2p38CKdj5KGXWMwRLjgFIlG.png', u'Детский мир': logobase + '00Vf3rPABNnbNQ6Rv0dnfcg3JsJelA.png', + u'Днепропетровский Государственный': logobase + 'DdzgOgvldXBS0n7Vhd78R464TeepUK.png', u'Дождь': logobase + '381.png', + u'Дождь HD': logobase + '381.png', u'Дом кино': logobase + 'jlC78Fy13KWjQUN6l3FtbsRLZDvc0x.png', u'Дом кино Премиум HD': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', u'Домашние животные': logobase + 'HiWAmn5RvUKNJnSW2Jhxjs6maoNFV7.png', @@ -434,6 +497,7 @@ u'Домашний (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Домашний +4': logobase + 'atZkgcX9EReSy2VkatZGbdQyBI2viX.png', u'Домашний магазин': logobase + 'yhzajV7vdLogQzMKat0rLJWaDkPpTP.png', + u'Дон 24': logobase + 'qB34tCfiy8RoCwBprbt3yBlb9QTb7i.png', u'Донбас': logobase + 'vsj1IA3Z8QVL3AzzuN4EshgT4LUmRr.png', u'Драйв ТВ': logobase + 'pmmgMKcRbxeYkjVUVr4IAWM0UuZHO4.png', u'Еврокино': logobase + '34mszCG0j0Vf6kFcMrLPnFEA8UPdu6.png', @@ -442,18 +506,21 @@ u'Еда HD': logobase + 'ojUD1jhpv7HBOLmubpEBOsANkpYNtk.png', u'Еда ТВ': logobase + 'TWdAdMXfMSylb2mQ4efFnOAYosymNC.png', u'Еспресо ТВ': logobase + 'lOwm890F5URuR5Ej7IacerzECPIDt4.png', + u'Жетысу': logobase + 'mLPPIv9pW09vUIdGp70OPsZPhMvUlo.png', u'Етно HD': logobase + '3SMfgieNX8dCez4flcM8TpCCePrgaq.png', u'Живая Планета': logobase + 'xgKSMwqBdEyXnbVgb8LtNXSMiaPcOx.png', + u'Живая природа HD': logobase + 'L9qmq6UIprbg4VBQA46tqRjQ22thsp.png', u'Живи': logobase + 'cOluSjslxxs3JZtSVO8c15xh7h8SDU.png', u'Загородная жизнь': logobase + 'cGGo8HRkVhy66UXKXZ4tH5HyUaaxJA.png', u'Звезда': logobase + '0HLRrFHt2QIkbJpLc1fy0RVe7hqCEC.png', u'Звезда (+2)': logobase + 'wa6skHc8W2MRz9Mm3qoKS4LpctSvYs.png', + u'Звезда (16:9)': logobase + 'q5gm6R9h3TAJ4oP5BSViBl0AUgARR3.png', u'Здоровое ТВ': logobase + '6x1zbASjWqScFk0bghS2RX3H2k5XZf.png', u'Зоо ТВ': logobase + 'RtAhntWPlKQs6CIYAb72piNF9EsN3E.png', u'Зоопарк': logobase + '1Ugpb5T1THFcFpn19Mnua21KxHkjct.png', u'Иллюзион+': logobase + '8LToTNvWRBHvb5IKoteKm8EwAGw8mv.png', u'Индиго': logobase + 'dFIp5shmC5DbfWIDVaFh7coAofmLON.png', - u'ИНДИЙСКОЕ КИНО': logobase + 'y8wvb5nc77vn5c4x8kinv85c7mv6n875c84.png', + u'Индийское кино': logobase + 'y8wvb5nc77vn5c4x8kinv85c7mv6n875c84.png', u'Интер': logobase + '3SP67FapzyZqMVZTPiJIcN09KRkTeu.png', u'Интер+': logobase + 'QEdaDBbqr13CCfwKQAP77UZYPQIPn0.png', u'Интерра ТВ': logobase + 'Akn9ntxqkSNrX3BTFRRtXPaEYBszjR.png', @@ -461,6 +528,7 @@ u'История': logobase + 'PNRaeOUFzOPFtrclFBBRTckj6Lvo0u.png', u'К1': logobase + 'mk2mYb28HFIxkFIiMNQWmKUdn1Y8hD.png', u'К2': logobase + 'IjG76jf8k8HTNLooNpUiEXtkPfA2rG.png', + u'Казахстан': logobase + 'NKuiJTbL08ogaXlIVZkqAYqBUOfFpC.png', u'Калейдоскоп ТВ': logobase + 'YJMqmzlZ87QeXYm9XpjH0XzpjljNcU.png', u'Карусель': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'Карусель (+3)': logobase + 'cYXQq7FnkEBAyYxfoPPsL2y3fwMDKU.png', @@ -473,6 +541,7 @@ u'Кино HD (резерв)': logobase + 'tJiazXmwL8E8jxdZZDCTCyCuD6iit6.png', u'Кино 2': logobase + '4liS5ZApeFUGXcGL6gFrbw7swx2ZaB.png', u'Кино ТВ': logobase + 'KkITMDICqC1erWdSqyOqoccqde2wHC.png', + u'Кино ТВ HD': logobase + 'lbFIxXL9QKDoAFjooAew0Hoq3mmNzu.png', u'Кинозал (Боевики)': logobase + 'w9lyoCqdUiL9m5aD6VWg9M6iZ188fd.png', u'Кинозал (Детский)': logobase + 'wFeLlGKHXw5b0hOT3sTFLtxE9qs9Om.png', u'Кинозал (Комедия)': logobase + 'cos0nJtFYR3FIqZrkNj7ZyJTj5uo6M.png', @@ -490,6 +559,7 @@ u'Кинохит': logobase + 'y3igbjZ&LGyqDWGIASUY78AWID.png', u'Конный мир': logobase + 'd8gVne1Em14rIfM6pSsiuufXZeYQqi.png', u'Конный мир HD': logobase + 'q87VlmfaBeBhq8SOhAu0SVPEGjiqWM.png', + u'Кот ТВ': logobase + 'Rtdtc632nbBQ2TsymgXhkOt7nhytjW.png', u'Красная линия': logobase + 'I43S6jd5noclar0LlPJnyY8adonmUV.png', u'Крик-ТВ': logobase + 'z4Iv85kqJXWHQJ5475DNxOxlo9Ty5v.png', u'Круг': logobase + 'fbzJYZiRscBiEvaWEFmzvYzEHdaNN7.png', @@ -499,8 +569,8 @@ u'Кто есть кто': logobase + 'MwNkO3fXd6KefRdiGlOdOQ5q0Zu7kS.png', u'Кубань 24': logobase + 'CAAqiN96tQzFDdtz3vjrrgeIjAKqNq.png', u'Кубань 24 Орбита': logobase + 'FauvJxsKmI5a1fR62uSH9hJfHs5TCr.png', - u'Культура Украина': logobase + 'pyKdve4YhoChQFGSha8J0FBWBf302a.png', - u'Купи Дом ТВ HD': logobase + '7CyfedwVltdaMcZXD3Xx7wa50mZJom.png', + u'Культура (Украина)': logobase + 'pyKdve4YhoChQFGSha8J0FBWBf302a.png', + u'Купи дом ТВ HD': logobase + '7CyfedwVltdaMcZXD3Xx7wa50mZJom.png', u'Курай ТВ': logobase + 'A0onEnFhSzqGxckeyyXsf0ZQ4R3Vc6.png', u'Кухня ТВ': logobase + 'G0WbVMphlP9oJ6KvHRfx0xDfhrF9Re.png', u'КХЛ HD': logobase + 'kRN7BwVtcdaXrU4Mdg24qhFAxjx9oZ.png', @@ -510,6 +580,7 @@ u'Ля-минор': logobase + '8FJA3xMMHcrZuGifHViyVQLjVIem5u.png', u'М1': logobase + 'ezvu2ugYMGnZ968LlnjPw7VjqWIPeM.png', u'М2': logobase + 'U4s78hznNz7mFYZQICkxN7J0HTtlCP.png', + u'Малыш': logobase + 'lawBkwFuj6qiu3jZQGLnHJUc9wGPwM.png', u'Малятко ТВ': logobase + 'kjYF9vS2IDTMehpzC7WWfjnZ4NVpuk.png', u'Мама': logobase + 'nw9fROQIjjKSDp8Wjkjl1Wt0n0xHxd.png', u'Матч ТВ': logobase + 'hQDOuQjUVczvUU2ocLE0tkC1siCqpo.png', @@ -534,36 +605,45 @@ u'Матч! Футбол 3': logobase + 'OLHdmyfUev4mMX0OGniJrlUwHnMKOg.png', u'Матч! Футбол 3 HD': logobase + 'OLHdmyfUev4mMX0OGniJrlUwHnMKOg.png', u'Мега': logobase + 'IXY7dRFoq0qCqn4UbY47iP36vVZ6ck.png', + u'Миллет': logobase + 'zDsxAdZp80tZ43Rd2IWAgFOFFYDkwh.png', u'Мир': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', u'Мир (+3)': logobase + 'QxOYkz6f80IdhmC4RSHI1cMd32CqYZ.png', u'Мир 24': logobase + 'auv6717gJOWi0A2VoeDQaCsx9G1NOj.png', u'Мир HD': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', - u'Мир белогорья': logobase + 'JRU6TyiBAX5Fk84DebhKeSxg7ZkrEf.png', + u'Мир Белогорья': logobase + 'JRU6TyiBAX5Fk84DebhKeSxg7ZkrEf.png', u'Мир сериала': logobase + 'n3zRAlCCBLl5WOeunWGpuMmfdvSJcW.png', u'Мир сериала': logobase + 'XWDsU7aoUyPPoKfcX7WMYQheuzHJL0.png', - u'Мир Увлечений': logobase + 'KtELENmDesBwzAAryLgJPqwlr9m17z.png', + u'Мир увлечений': logobase + 'KtELENmDesBwzAAryLgJPqwlr9m17z.png', u'Многосерийное ТВ': logobase + '4TMYdVpZYXafyIumuB5d7PrjFnslyT.png', u'Морской': logobase + 'uzlb3awoyNqvIcf6i35hTVqf7gvuqz.png', u'Москва 24': logobase + 'dZcmoqRoZLhCBh8BE4RnbQivuDY6hH.png', u'Москва Доверие': logobase + '9oPazhJQrGZcSN64ZOS3WjLwGmQIZy.png', u'Моя планета': logobase + 'Qa41eifERrD77xQsmpRGbeTq95Ldlv.png', - u'МУЖСКОЕ КИНО': logobase + 'y997iu65e65h4w5d3s4dy.png', + u'Мужское кино': logobase + 'y997iu65e65h4w5d3s4dy.png', + u'Мужское кино HD': logobase + 'xJ3q8xhZTwU3YuZK0CDslvZBNTOUwc.png', u'Мужской': logobase + '6YbhuWNqPKQWWsUGbBnSbAbm7IGssX.png', u'Муз ТВ': logobase + 'gttVvZmkAklbl2i0Mqy1MCzSCn7WiY.png', + u'МузСоюз': logobase + 'E4a3fEpdSy2c8AnYEdR8ZIFgFe2LAP.png', u'Музыка Первого': logobase + 'fD2Hnsq5BPMGvobLDMPZP049yNhBYt.png', u'Мульт': logobase + 'ZVzHvGF8mZ6RTsSh6aWsPbF1FBLjyp.png', u'Мультимания': logobase + '132.png', u'Мультимания HD': logobase + 'xrR3K6dBvelv1nZos2kTbTiU3QnJVK.png', u'Надежда': logobase + 'fvCkzRJPsTx4HONWPv3vPMjQNloPBT.png', u'Нано ТВ': logobase + 'QuURIfJUmXegxsHMYqMivVwxizbfKd.png', + u'Настоящее время HD': logobase + 'Hhq50Ibui6mxfr6tZxKGy9fu2iiBAt.png', u'Наука 2.0': logobase + 'ypWbqYqKApM8cnDK1FibvQgpmgEay9.png', + u'Наш детектив HD': logobase + 'ORgdUno4IvNQYiShiEY4srF16JguLs.png', + u'Наш кинороман HD': logobase + 'NyV9o2C4xIMFxxYUQsS1qsdAtkqz0k.png', + u'Наш футбол HD': logobase + 'w13cg41Ar3O5MKHWCtYfBWwBBTTial.png', u'Наше HD': logobase + 'NyV9o2C4xIMFxxYUQsS1qsdAtkqz0k.png', u'Наше любимое кино': logobase + 'LSR5M6VxB0YDwv6803zrGFkq7vGQ3J.png', + u'Наше новое кино': logobase + 'y5jtghJCHG65ukyfjv 45stjxc76.png', u'НАШЕ НОВОЕ КИНО': logobase + 'y5jtghJCHG65ukyfjv 45stjxc76.png', u'Наше ТВ': logobase + 'O7Wx8kIB2aXEEmHLwBFIUhxn8B5WQF.png', u'Наше ТВ': logobase + 'gTfxpSWtOGWBq7UHAHcz3XF10bzg78.png', u'Ника ТВ': logobase + 'pb3d3rBN4qW7ggzsosbAZXflfIv0Ty.png', u'НЛО ТВ': logobase + '2VGhYruaQo19G1NLGoOiTrwmPxef7d.png', + u'Новое телевидение': logobase + 'n1SkVllOU0IGIx6P2YpIFvNLMmsHfm.png', u'Новый канал': logobase + 'k7YdHhVpFZPIkBMXS2P2O2TkZSPf0y.png', u'Новый Христианский': logobase + 'gf6hTOcGXasvr47vTFRYZGV11xkDr5.png', u'Ностальгия': logobase + 'tIfiXoDaXoZevuGu9pZJSvX8unv1xl.png', @@ -576,38 +656,50 @@ u'НТВ (+4)': logobase + 'FeYewxBbj2Cz8NOJDl98PTxYXI8cPg.png', u'НТВ (16:9) ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'НТВ +7': logobase + 'f5N5GvTcqD5JLvdeRPha6LtkgUroht.png', + u'НТВ (+7)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', + u'НТВ (резерв)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', u'НТВ HD': logobase + 'zdJ3ye6d3UWl5a56zm6LjqYH6ziSOs.png', u'НТВ Мир': logobase + 'ykpU49Gl1akVkgiVSsCm5B4TjXvQ64.png', + u'НТВ Мир Балтия': logobase + 'ykpU49Gl1akVkgiVSsCm5B4TjXvQ64.png', + u'НТВ Право': logobase + '7yNDqWT8KiQ2aa9kGYXSsnVFCPj5xx.png', + u'НТВ+ Инфоканал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'НТВ+ Баскетбол': logobase + 'bIWuyv7DJ65D5hIANkeo9SyIHGUXtn.png', u'НТВ-ПЛЮС Инфоканал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'НТН (Украина)': logobase + 'LpQE1Odb1EoH5dJ90gWjItVyEYBXsw.png', + u'О2 ТВ HD': logobase + 'loCx0MeuHyjR95aFi4EBu0s2xZP50i.png', u'ОНТ (Одесса)': logobase + 'P3tnfUDZ7f025rw6X7rFNT4aYmwMlJ.png', - u'ОНТ Беларусь': logobase + 'a84If8XdqSFa6nHpegdujt52vAGNJW.png', + u'ОНТ (Беларусь)': logobase + 'a84If8XdqSFa6nHpegdujt52vAGNJW.png', u'Оплот 2': logobase + 'EqwpuUgrI6Wl6JVDK2fLtXkNaqOXeU.png', u'Оплот ТВ': logobase + 'gvofGxTug45qSt1vsX0BPzQxGTrwTr.png', u'Оружие': logobase + 'CyDUCmYXK8WS2kXCX5kiAOFejnlwoP.png', u'Остросюжетное HD': logobase + 'mxF7CZsqsDRMMK4pN8ekdccEgvEsZC.png', + u'ОТВ (Приморск)': logobase + 'W3FNGLfo7R7JpYI98Am5sGamz0bfaN.png', + u'ОТВ (Приморье)': logobase + 'W3FNGLfo7R7JpYI98Am5sGamz0bfaN.png', + u'Открытый HD': logobase + 'CdIvMNsa7gAihJnpAKX8QnMdpvhAXc.png', u'Открытый мир': logobase + 'VX8Nc6TW79bbns9IgJI6OdIj0r3DPG.png', u'ОТР': logobase + 'CqxKorK72v3ULbWkB3ZOhdte0duYZa.png', u'Охота и рыбалка': logobase + '5l2P20J6ebTh0ptOr27Hh704niP3nU.png', u'Охотник и рыболов': logobase + 'Ws2ddPI0b5Ie7PymoPUsboVlz9lYMS.png', + u'Охотник и рыболов HD': logobase + 'O0wexOqiQXKMwr2coDMKWvJEb4zLQ1.png', u'Парк развлечений': logobase + 'beyfqyeacrFG0PrOeKUQhzQ4bV6Q5d.png', u'Первый городской Одесса': logobase + 'vBOI3YTA4FDLD0c7BHHjq476p9GMCZ.png', u'Первый деловой': logobase + 'a1Qf3MpxC9FPD68Tj8vtUTNK8P25xr.png', - u'Первый деловой': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Первый канал': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (+2)': logobase + 'Lo2zy8h0msIieULsbHOjV1oSInHogr.png', u'Первый канал (+2)': logobase + '7qA89ulmkQx43LT4XRkDiOepVnUBJ6.png', u'Первый канал (+4)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (+4)': logobase + 'nHJycH0CkOhPeZ9DmB47iSMWP5HyWz.png', + u'Первый канал (+4) (4:3)': logobase + 'nHJycH0CkOhPeZ9DmB47iSMWP5HyWz.png', + u'Первый канал (+4) HD': logobase + 'NW6N6mMIXx1QVn6SyNYdwumvL1ZWN9.png', u'Первый канал (+6)': logobase + '45vL1pa1DYHjYyMs1dtGC9RsACLmz3.png', u'Первый канал (+8)': logobase + 'lRjN5HOOX0txLz8nigpKqpVw5CBKJZ.png', - u'Первый канал (4:3)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Первый канал (4:3)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (Евразия)': logobase + 'oPB8TcSFAuJtk4hBWgAqC9yNjMpfsi.png', u'Первый канал (Европа)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (Молдова)': logobase + 'UsbyyrSaJmdjntY4muATwDytnseYYB.png', u'Первый канал (СНГ)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал HD': logobase + 'VxAFWzh1y88c8Aqa17TsxD2IO5pqoi.png', + u'Первый канал HD (резерв)': logobase + 'wT8vUxakbehUwgX5lI9qoRAFBa74tx.png', u'Первый канал [16:9]': logobase + 'VGBjnqmDOxPOgDXK0seYUkmVloEpr2.png', u'Первый Метео': logobase + 'vdA7FKd1SCYhX4ovvzjQEHFdW3uA5X.png', u'Первый музыкальный HD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', @@ -621,8 +713,8 @@ u'Перец (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ПИК': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', u'ПИК HD': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', - u'ПИК HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Пиксель ТВ': logobase + 'BdCXB7wPZMNvlWzB5xEFzmsYUXcfXW.png', + u'Пингвин Лоло': logobase + 'Z9JfLsdRewyP2JZjxDRs8lVWz6bMhX.png', u'Планета': logobase + 'o7wnxC58C4KfCnX0hh0il0LTRlPfSg.png', u'Планета HD': logobase + '1QjirpCLi3q9qPu1CTvEvCB0BfINeo.png', u'ПлюсПлюс': logobase + '6gVIy7RMokFO61iVawgwbthe5mhgqm.png', @@ -632,30 +724,38 @@ u'Продвижение ТВ': logobase + 'A13CkivHlwonag4N6pzFujtV00lwLL.png', u'Просвещение': logobase + 'Fpx3Vqqk2VNcXl4YjsfO53XscWadvF.png', u'Психология 21': logobase + 'AyLAdiqcKu5X8ykdLf2bO9HsxMlJdO.png', + u'Пушкин - Царское село': logobase + 'Oq0JHpGCMDNzpjf85X7Az3PPPkmDs5.png', u'Пятница': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', u'ПЯТНИЦА ( +4 )': logobase + 'HjLiq5t608j5kH6yjSiodCoFMUYwQL.png', u'Пятница (+2)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', + u'Пятница (+4)': logobase + 'hCwKaMG6bApehKr68iauaJa68ZC3W1.png', u'Пятница (+7)': logobase + '3UjE5kGjFxeXtseV0TlUTAHv71aQGO.png', u'Пятый канал': logobase + 'nIUDYY41OO4Xo0ntGpGv2rfpOR5ngt.png', u'Пятый канал (+2)': logobase + 'rsEtkBk2ta1Wj1Y8uvxvSm4Vmbycir.png', u'Пятый канал (+7)': logobase + 'q0XABDTYA29I5V6AMEqFQDdcr5Bj9k.png', u'Пятый канал +4': logobase + 'jLZJgPYRXOQWt6vmNpisLyb0HC6wnT.png', - u'Рада Украина': logobase + 'hBFJBYNiqZUom0ooVtNEJKliZwfioO.png', + u'Рада (Украина)': logobase + 'hBFJBYNiqZUom0ooVtNEJKliZwfioO.png', u'Радость моя': logobase + 'VRylZFYgFq7AL0FWcbf5JVOX3desn3.png', u'Раз ТВ': logobase + 'CWaPSXdAL4Ejo1wNOqSWNPNVwbbhC7.png', u'РБК': logobase + 'JUMDXZxxB3UiVpMpU8t0aCpbVzxTmP.png', u'РЕН ТВ': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', u'РЕН ТВ ( +4 )': logobase + 'n0OQ3o7skZJ0B9Htlj01o2VDeXKj0f.png', + u'РЕН ТВ (+4)': logobase + 'lXDeqkRS52EaZoEa7JmoJfzWoGnQ2m.png', + u'РЕН ТВ (резерв)': logobase + 'KHrPZNDBZ3u1RgGGFVULyXl1HveoqO.png', u'РЕН ТВ (+2)': logobase + 'WeHcyu6MG9GGpCmToRQnoL97iesKk9.png', u'РЕН ТВ (+7)': logobase + 'bOa17Taj05Dr1k29vFt0hdbOqCeqGQ.png', u'РЕН ТВ [16:9]': logobase + '8KwidLyxxFby0hoExGeUh4pbaetC16.png', + u'РЕН ТВ HD': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', u'Ретро ТВ': logobase + 'nf4fvx9VYrz9U4cJZv2xTx09ejd1SE.png', + u'Ретро-ТВ': logobase + 'Of6VAxft82OXf1hk4UonRCHTw3MzqD.png', u'РЖД ТВ': logobase + 'qa6177IUBWR0hYt1HKXxlMrF5MStOb.png', + u'РЖД ТВ HD': logobase + 'qa6177IUBWR0hYt1HKXxlMrF5MStOb.png', u'РИА Новости': logobase + 'DixgG6tVZzcVHO2LPQEx3QrtfoVah3.png', u'Рим ТВ': logobase + '0bwbLdfI8rl5xpxQb3gy1NPxoGh2Ie.png', - u'РОДНОЕ КИНО': logobase + 'y7saqicb538iqo64ho46hh46.png', + u'Родное кино': logobase + 'y7saqicb538iqo64ho46hh46.png', u'Россия 1': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', u'РОССИЯ 1 ( +4 )': logobase + 'jy8HZ6Xf6hroTuXmjqSUgawX7O0g9t.png', + u'Россия 1 (+4)': logobase + 'DL17FIS3R8m6eWTwFvdDYualmxvkGV.png', u'Россия 1 (+2)': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', u'Россия 1 (+2)': logobase + 'IVnok4EXJmqeIq8Kppprl456zRybNO.png', u'Россия 1 (+6)': logobase + 'vWy8vur5mokNIzuCKPZpOnj59w8TP8.png', @@ -666,10 +766,13 @@ u'Россия К': logobase + 'M0fzdXnwAvdXUdIMtyWGoGmCq2BTTa.png', u'Россия К (+2)': logobase + 'rVrDeCUhT4RPKIa2h9qRtnDon3Pd9X.png', u'Россия РТР (Украина)': logobase + '5o9OWeEw90hM5ouECuTLwj5QP8MwU3.png', + u'Россия РТР': logobase + '5o9OWeEw90hM5ouECuTLwj5QP8MwU3.png', u'РТР Планета': logobase + 'wJMTJDEEwIhJWhNL0c6kfs5HVVJOVx.png', + u'Рудана': logobase + '62hGDWG6c90mWQyu1m65r4dpEqe24Y.png', u'Русская комедия': logobase + '4Q3vGcJh5o0cgJB18scb90ogvL6OYV.png', u'Русская ночь': logobase + '9Sh9bJuj6js5AJsypAd6UvwnsIB25R.png', u'Русский бестселлер': logobase + 'b5JXaosgmcanh9EVJg52yBefvdLQF7.png', + u'Русский бестселлер Int.': logobase + 'b5JXaosgmcanh9EVJg52yBefvdLQF7.png', u'Русский детектив': logobase + '7I7VjbsFMIkZdoSbHFXiKEVZKNUbOM.png', u'Русский иллюзион': logobase + 'E9Imfr8aHN5midPVpNhJ3fo49FHbQE.png', u'Русский роман': logobase + '2smriIFxtj7Ojh4jyZq0K1XrT98XjS.png', @@ -677,8 +780,10 @@ u'Русский экстрим': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Русский экстрим HD': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Рыжий': logobase + 'wfBSy60qHaPSKPpTfrNv9Q167iHIPu.png', + u'Самара ГИС': logobase + 'F0vMjPoFdXa0Vh5t17AOwJVJziMBPG.png', u'Санкт-Петербург': logobase + 'sb81YtPOvlHidztMnC5tZPSKkb1uMI.png', u'Сарафан ТВ': logobase + 'LsYzwEOUspoxkY2hrTSy9zKqvpWlY8.png', + u'Смайлик ТВ HD': logobase + 'cebz4lM1usjLx0qmFnQTn4Ty1yT0G3.png', u'Семейное HD': logobase + 'ORgdUno4IvNQYiShiEY4srF16JguLs.png', u'Сказка HD': logobase + 'dxhsesilX4JjwoCc8Qab4SYe8fxm75.png', u'Смайл-ТВ': logobase + 'AFH2ud3ZiC5BXXPoq9OVbhCQTS0yOF.png', @@ -689,23 +794,32 @@ u'Спорт 1 (Украина)': logobase + 'XqwvMS8Hn0mOpbh79esrIqELTsvo5b.png', u'Спорт 2 (Украина)': logobase + 'q0PokCXx6jtCHEPMvE42I0pD3ZNY0o.png', u'СТБ': logobase + 'saZlIDrdaXWoiQa8sfZp2bEAeH0kXk.png', + u'СТВ': logobase + 'W1RY5hkIyvOOr2d8XT6GisDsIFlpbS.png', u'Страна': logobase + '5G27bahViND43dD1VlkaKlQRsYOqwL.png', + u'Страна FM ТВ': logobase + 'ysrRW9deFkccNGlhtT0Sww5Yt8IpY1.png', u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', + u'СТРК HD Сочи': logobase + '2PphESGueDUS1T6wSOr12iTgTbIVJm.png', u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', + u'СТС (+1)': logobase + 'WD2ki7yenlw9xaSzIjSEJfEHsD4cmo.png', u'СТС (+2)': logobase + 'FWNPupxL8N0STWKYEryyiV5sDFN2tS.png', u'СТС (+2)': logobase + 'CPz3EbrU8SxT3CFj4JXtsx5YBJX3DH.png', + u'СТС (+4)': logobase + '9kPWMgG96ZZcBeleMogGOHKZcsOARf.png', + u'СТС (+7)': logobase + 'qxxLwx53wKNngJTCtT126zgZT5Wi1x.png', + u'СТС (резерв)': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', + u'СТС International': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', u'СТС (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'СТС Love': logobase + 'iciJHbEmJ1hHXAMhzC9cRWhmh9gH0L.png', u'СТС Love (+7)': logobase + 'HVMQnlAIMnDToeJqFdxKojHUGA0QT1.png', u'ТА-Одесса': logobase + 'xnCANqGy0KUMtzCttmO9jyZUsXlEEI.png', u'Тагил ТВ': logobase + 'ubegTi3hkdx7xtZCdOz8K0gBQLlght.png', u'ТБН': logobase + 'r9O7HmwQbFR4oKMH9yKAogE8xBzwz4.png', - u'ТБН': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ТВ 3': logobase + '427.png', u'ТВ 3 (+2)': logobase + '427.png', u'ТВ 3 (+3)': logobase + '427.png', + u'ТВ 3 (+7)': logobase + 'HfEo6PctsuTnlPQIa0xVKgEDqFwI48.png', u'ТВ3 ( +4 )': logobase + '7A2g2KEIG6GRcHWjGYCz73jymxAZP1.png', u'Тверской проспект': logobase + 'k6RPWpDIwfsOZ5qkqHF5ZdOf5GqfmH.png', + u'твоЁтв': logobase + 'sRaEbhHc5usYZI5PWmXlZNPWOFI9qI.png', u'ТВЦ': logobase + 'QEpQTskZ9hcfI0rgD8osHVYSv58pde.png', u'ТВЦ (+2)': logobase + '7zGrgQeXL61DZu4RBOo3KDwnlOjymw.png', u'ТВЦ (+4)': logobase + 'dR7hMBOIq0MDGMkydFuksHGLNIWz7U.png', @@ -729,23 +843,29 @@ u'ТНТ (+3)': logobase + 'XIqPuPa3zUzZFevriI0urGganhT8Hm.png', u'ТНТ (+4)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', u'ТНТ (+7)': logobase + '3nipGCZNvlMHrtgLyraU5YEA6sj0ek.png', + u'ТНТ (резерв)': logobase + 'yEDrU5cgZbdUgfq8kzb40581xLcXNy.png', + u'ТНТ HD': logobase + '8yXyL2mLOMBk3MwA7oeezH4DULY4Ut.png', u'ТНТ International (Европа)': logobase + 'd4loXdWqOPiwF7thzyBXX8JSspfVjU.png', u'ТНТ-Music': logobase + '6Go23tY9hpakfrvTEUH7Z7o5Y9hpOG.png', u'ТНТ4': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', u'ТНТ4 ( +4 )': logobase + '14QPJWtD2gaXxRFVr2v9YxkvwDiT0O.png', u'ТНТ4 (+2)': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', u'ТНТ4 (+3)': logobase + 'dg9BEAtJr4IthpXjxV8NGAiu95NjXl.png', + u'ТНТ4 (+4)': logobase + 'X8ny4KVoh9socLzE8nmCIIEz2xVXBO.png', u'ТНТ4 (+7)': logobase + 'QMacW5XucDi08h4stQ3AGt69IYAr97.png', + u'Томское время': logobase + '1Z7yLtfyuITQhXchO59gwyQ3mES7qS.png', u'Тонус ТВ': logobase + 'bE8WfReOerYTIbqPOo6VD2ajrFdOBT.png', u'Точка ТВ': logobase + 'JWwPbPnkWooIpKd5WYsdpfO3Mh14oA.png', + u'Третий Цифровой Одесса': logobase + 'MXcua7OlJ9CplpD15hD84Xn2QjoCdt.png', u'Третий цифровой (Одесса)': logobase + 'UERKEoCARXOG4CveFzUNnJoM9eOSwh.png', + u'ТРК 555': logobase + 'z3GvW4WdjwSmsQrfhAIZewEsdH0rsi.png', u'ТРК Киев': logobase + 'qW0p5z3De7COmSxTmvJ4ZA2wOuSJjg.png', u'ТРК Украина': logobase + '0co3dwhFDhoCVeTbfMV8ASYFYxSrWM.png', u'ТРО Союза': logobase + 'xAXy9iMyJ4wa2wmugJvbZuDIzc9pVz.png', u'Трофей': logobase + 'tQTWwjNBC8aLLWiWZfCj43BhWNH51I.png', - u'Трофей': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Трофей HD': logobase + 'cPi9B0icZpuvSRQiH0Kk6rNY8r1X65.png', u'ТТС': logobase + 'crsSIipA6N288sjn4EvUyOyTd0ed9A.png', + u'Туран TV': logobase + 'keRiJb6tKMYm5xhnp53iltppz70qV7.png', u'Улыбка ребенка': logobase + 'P8aPFN50uJWJHkrqFGb7wgzfaTHUOO.png', u'Унiан': logobase + 'fhpFrTDoI9xx7UlK65KAjAbdTGehLL.png', u'Усадьба': logobase + '5yIxLQzQyZnH5EJcwpSGb28QuRTSFH.png', @@ -764,6 +884,8 @@ u'ЧГТРК Грозный': logobase + 'LqDNdQj6nf4MraZztT6ZnACn7yOJpV.png', u'Че': logobase + 'Hv36ZG48lg1mm2wdxAo3ju1EFS41Ga.png', u'Че (+2)': logobase + 'N7R86wgoPSELhZ9jCeqaykayjU8mbB.png', + u'Че (+7)': logobase + 'fV6EjhiQaVyMMlk1era5UxYZOKX7Rf.png', + u'Черновицкий Проминь HD': logobase + 'Us5Sr5jEx6SA6eBdZA9xchHzj1hJ4t.png', u'Черноморская телекомпания': logobase + 'LJVoPVcJAE0s4DNmXCga0UPZkRkLTq.png', u'ЧП-Инфо': logobase + 'Xy7mLl3exaBKuLlDGdrRls6hyR7mSw.png', u'Шансон ТВ': logobase + 'VY0TyCCkKOj5b8BhBJjT020sQoxL9F.png', From 37179ef82283dd5ffdfe2d7f8da35be92edcc137 Mon Sep 17 00:00:00 2001 From: zixelmike Date: Thu, 16 Mar 2017 23:21:14 +0300 Subject: [PATCH 89/95] Update torrenttv.py --- plugins/config/torrenttv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 749bcc4..31c1a1c 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -65,6 +65,7 @@ u'Amedia 1 HD': logobase + 'KPN6ULwGzTNbKziTI9sN7xfFPhtRzc.png', u'Amedia 2': logobase + 'fAvxTQbWu0DAcMkqej0m73KohAcQJw.png', u'Amedia Hit': logobase + '3lKypp7zXsJ3FDusXzKw9hHnVLythX.png', + u'Amedia Hit HD': logobase + 'HdnTfcZCgP7Odm1cOKNq9j4yJDRiFP.png', u'Amedia Premium': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Amedia Premium HD': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Ani': logobase + 'vui1cRrE05CZv1N9Qb20jJ6mTFOJue.png', From 38dffaa52a53b86706843d549ecd7eb5857567b6 Mon Sep 17 00:00:00 2001 From: zixelmike Date: Sun, 19 Mar 2017 14:29:56 +0300 Subject: [PATCH 90/95] Update torrenttv.py --- plugins/config/torrenttv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 31c1a1c..942dd12 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -798,6 +798,7 @@ u'СТВ': logobase + 'W1RY5hkIyvOOr2d8XT6GisDsIFlpbS.png', u'Страна': logobase + '5G27bahViND43dD1VlkaKlQRsYOqwL.png', u'Страна FM ТВ': logobase + 'ysrRW9deFkccNGlhtT0Sww5Yt8IpY1.png', + u'Страшное HD': logobase + 'Ce9qZfZQAZ8gLs1fb2WCamhsqB6xQN.png' u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', u'СТРК HD Сочи': logobase + '2PphESGueDUS1T6wSOr12iTgTbIVJm.png', u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', From 1fea6cbc1a41faf4726614614c79bdf7ea954886 Mon Sep 17 00:00:00 2001 From: zixelmike Date: Sun, 19 Mar 2017 14:35:14 +0300 Subject: [PATCH 91/95] Update torrenttv.py --- plugins/config/torrenttv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 942dd12..6deff89 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -798,7 +798,7 @@ u'СТВ': logobase + 'W1RY5hkIyvOOr2d8XT6GisDsIFlpbS.png', u'Страна': logobase + '5G27bahViND43dD1VlkaKlQRsYOqwL.png', u'Страна FM ТВ': logobase + 'ysrRW9deFkccNGlhtT0Sww5Yt8IpY1.png', - u'Страшное HD': logobase + 'Ce9qZfZQAZ8gLs1fb2WCamhsqB6xQN.png' + u'Страшное HD': logobase + 'Ce9qZfZQAZ8gLs1fb2WCamhsqB6xQN.png', u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', u'СТРК HD Сочи': logobase + '2PphESGueDUS1T6wSOr12iTgTbIVJm.png', u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', From 1599f61adc121caafe7f7a2aca2ae5d7e74cb898 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Wed, 7 Jun 2017 12:52:35 +0300 Subject: [PATCH 92/95] Temporary disabled CID generation WEB API --- acehttp.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/acehttp.py b/acehttp.py index 8914490..a6119a2 100755 --- a/acehttp.py +++ b/acehttp.py @@ -415,16 +415,17 @@ def getCid(self, reqtype, url): if url.startswith('http'): if url.endswith('.acelive') or url.endswith('.acestream'): try: - req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) - f = base64.b64encode(urllib2.urlopen(req, timeout=5).read()) - req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) - req.add_header('Content-Type', 'application/octet-stream') - cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] + #req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) + #f = base64.b64encode(urllib2.urlopen(req, timeout=5).read()) + #req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) + #req.add_header('Content-Type', 'application/octet-stream') + #cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] + cid = '' except: pass if cid == '': - logging.debug("Failed to get CID from WEB API") + #logging.debug("Failed to get CID from WEB API") try: with AceStuff.clientcounter.lock: if not AceStuff.clientcounter.idleace: From f9c6419bde799bb62ccbdfb0910fd55c6673cab6 Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Fri, 9 Jun 2017 07:25:19 +0300 Subject: [PATCH 93/95] Returned changes after acestream CDN was repiared http://forum.torrentstream.org/index.php?topic=57.msg24773#msg24773 --- acehttp.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/acehttp.py b/acehttp.py index a6119a2..4f8a453 100755 --- a/acehttp.py +++ b/acehttp.py @@ -415,17 +415,16 @@ def getCid(self, reqtype, url): if url.startswith('http'): if url.endswith('.acelive') or url.endswith('.acestream'): try: - #req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) - #f = base64.b64encode(urllib2.urlopen(req, timeout=5).read()) - #req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) - #req.add_header('Content-Type', 'application/octet-stream') - #cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] - cid = '' + req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) + f = base64.b64encode(urllib2.urlopen(req, timeout=5).read()) + req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) + req.add_header('Content-Type', 'application/octet-stream') + cid = json.loads(urllib2.urlopen(req, timeout=3).read())['content_id'] except: pass if cid == '': - #logging.debug("Failed to get CID from WEB API") + logging.debug("Failed to get CID from WEB API") try: with AceStuff.clientcounter.lock: if not AceStuff.clientcounter.idleace: From 08fbcbe6529f459a83e24c19351d686202b2904e Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Thu, 28 Sep 2017 23:10:34 +0300 Subject: [PATCH 94/95] Add HTTP/SOCKS PROXY for AllFon,TorrentTelik & TorrentTV plugins (#27) * Add Proxy support for playlists requests Need to install python requests !!! For example you can install tor browser and add in torrc SOCKSPort 9050 -> proxies = {'http' : 'socks5://127.0.0.1:9050', 'https' : 'socks5://127.0.0.1:9050'} If your http-proxy need authentification -> proxies = { 'https' : 'https://user:password@ip:port' } --- plugins/allfon_plugin.py | 26 +++++++------- plugins/config/allfon.py | 11 ++++-- plugins/config/torrenttelik.py | 8 ++++- plugins/config/torrenttv.py | 17 ++++++--- plugins/p2pproxy_plugin.py | 15 ++++---- plugins/torrenttelik_plugin.py | 44 +++++++++++++++-------- plugins/torrenttv_plugin.py | 65 ++++++++++++++-------------------- 7 files changed, 104 insertions(+), 82 deletions(-) diff --git a/plugins/allfon_plugin.py b/plugins/allfon_plugin.py index 27d679d..5365586 100755 --- a/plugins/allfon_plugin.py +++ b/plugins/allfon_plugin.py @@ -4,8 +4,8 @@ ''' import re import logging -import urllib2 import urlparse +import requests import time from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator @@ -17,7 +17,7 @@ class Allfon(AceProxyPlugin): # ttvplaylist handler is obsolete handlers = ('allfon',) - logger = logging.getLogger('plugin_allfon') + logger = logging.getLogger('Plugin_Allfon') playlist = None playlisttime = None @@ -26,12 +26,18 @@ def __init__(self, AceConfig, AceStuff): def downloadPlaylist(self): try: - Allfon.logger.debug('Trying to download playlist: ' + config.url) - req = urllib2.Request(config.url, headers={'User-Agent' : "Magic Browser"}) - Allfon.playlist = urllib2.urlopen(req, timeout=10).read() + Allfon.logger.debug('Trying to download AllFonTV playlist') + self.headers = {'User-Agent' : "Magic Browser", + 'Accept-Encoding': 'gzip'} + if config.useproxy: + r = requests.get(config.url, headers=self.headers, proxies=config.proxies, timeout=30) + else: + r = requests.get(config.url, headers=self.headers, timeout=10) + Allfon.playlist = r.text.encode('UTF-8') + Allfon.logger.debug('AllFon playlist ' + r.url + ' downloaded !') Allfon.playlisttime = int(time.time()) except: - Allfon.logger.error("Can't download playlist!") + Allfon.logger.error("Can't download AllFonTV playlist!") return False return True @@ -52,12 +58,8 @@ def handle(self, connection, headers_only=False): if headers_only: return; - # Match playlist with regexp - matches = re.finditer(r'\#EXTINF\:0\,(?P\S.+)\n.+\n.+\n(?P^acestream.+$)', Allfon.playlist, re.MULTILINE) - - add_ts = False try: if connection.splittedpath[2].lower() == 'ts': @@ -65,7 +67,6 @@ def handle(self, connection, headers_only=False): except: pass - playlistgen = PlaylistGenerator(m3uchanneltemplate=config.m3uchanneltemplate) for match in matches: playlistgen.addItem(match.groupdict()) @@ -73,6 +74,5 @@ def handle(self, connection, headers_only=False): url = urlparse.urlparse(connection.path) params = urlparse.parse_qs(url.query) fmt = params['fmt'][0] if params.has_key('fmt') else None - header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) + header = '#EXTM3U url-tvg="%s" tvg-shift=%d deinterlace=1 m3uautoload=1 cache=1000\n' %(config.tvgurl, config.tvgshift) connection.wfile.write(playlistgen.exportm3u(hostport, header=header, add_ts=add_ts, fmt=fmt)) - diff --git a/plugins/config/allfon.py b/plugins/config/allfon.py index d458e15..ade6c8b 100644 --- a/plugins/config/allfon.py +++ b/plugins/config/allfon.py @@ -1,13 +1,20 @@ ''' Allfon.tv Playlist Downloader Plugin configuration file ''' +# Proxy settings. +# For example you can install tor browser and add in torrc SOCKSPort 9050 +# proxies = {'http' : 'socks5://127.0.0.1:9050','https' : 'socks5://127.0.0.1:9050'} +# If your http-proxy need authentification - proxies = { 'https' : 'https://user:password@ip:port' } +useproxy = False +proxies = {'http' : 'socks5://127.0.0.1:9050', + 'https' : 'socks5://127.0.0.1:9050'} # Insert your allfon.tv playlist URL here -url = 'http://allfon.org/autogenplaylist/allfontv.m3u' +url = 'http://allfon-tv.pro/autogenplaylist/allfontv.m3u' # EPG urls & EPG timeshift tvgurl = 'http://www.teleguide.info/download/new3/jtv.zip' tvgshift = 0 # Channel template -m3uchanneltemplate = '#EXTINF:-1 tvg-name="%(tvg)s",%(name)s\n%(url)s\n' \ No newline at end of file +m3uchanneltemplate = '#EXTINF:-1 tvg-name="%(tvg)s",%(name)s\n%(url)s\n' diff --git a/plugins/config/torrenttelik.py b/plugins/config/torrenttelik.py index 5dce26e..81cd953 100644 --- a/plugins/config/torrenttelik.py +++ b/plugins/config/torrenttelik.py @@ -2,7 +2,13 @@ ''' Torrent-telik.com Playlist Downloader Plugin configuration file ''' - +# Proxy settings. +# For example you can install tor browser and add in torrc SOCKSPort 9050 +# if you use tor on the same machine with AceProxy - proxies = { 'https' : 'socks5://127.0.0.1:9050' } +# If your http-proxy need authentification - proxies = {https' : 'https://user:password@ip:port'} +useproxy = False +proxies = {'http' : 'socks5://127.0.0.1:9050', + 'https' : 'socks5://127.0.0.1:9050'} # Channels urls url_ttv = 'http://torrent-telik.com/channels/torrent-tv.json' url_mob_ttv = 'http://torrent-telik.com/channels/mob-torrent-tv.json' diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index 6deff89..e86d0e4 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -1,14 +1,21 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- ''' Torrent-tv.ru Playlist Downloader Plugin configuration file ''' +# Proxy settings. +# For example you can install tor browser and add in torrc SOCKSPort 9050 +# proxies = {'http' : 'socks5://127.0.0.1:9050','https' : 'socks5://127.0.0.1:9050'} +# If your http-proxy need authentification - proxies = {'https' : 'https://user:password@ip:port'} +useproxy = False +proxies = {'http' : 'socks5://127.0.0.1:9050', + 'https' : 'socks5://127.0.0.1:9050'} # Insert your Torrent-tv.ru playlist URL here -url = '' +url='' # TV Guide URL -tvgurl = 'http://api.torrent-tv.ru/ttv.xmltv.xml.gz' +tvgurl = 'http://1ttvapi.top/ttv.xmltv.xml.gz' # Shift the TV Guide time to the specified number of hours tvgshift = 0 @@ -20,7 +27,7 @@ updateevery = 0 # Channel logos mapping -logobase = 'http://torrent-tv.ru/uploads/' +logobase = 'http://1ttv.org/uploads/' logomap = { u'0x0 Fireplace HD': logobase + 'H1VboxDJC7sE7x3nKXoYT0X5r4LIqD.png', u'0x0 Music HD': logobase + 'hFj4tnC5uqAgpod3doHnJGZxgZXaiP.png', @@ -899,4 +906,4 @@ u'Ювелирочка': logobase + 'pvKHFUCv25R51hJoUpAJfvjGnWyqoZ.png', u'Юмор ТВ': logobase + '6VFA1SVxeFHUsGaKPbNxWZREDkGeZw.png', u'Ямал Регион': logobase + 'xapccCaMjlT6JEAkZmk27wzCXlEU2m.png' -} +} \ No newline at end of file diff --git a/plugins/p2pproxy_plugin.py b/plugins/p2pproxy_plugin.py index 33c3ed4..b93e726 100755 --- a/plugins/p2pproxy_plugin.py +++ b/plugins/p2pproxy_plugin.py @@ -40,7 +40,7 @@ def __init__(self, AceConfig, AceStuff): super(P2pproxy, self).__init__(AceConfig, AceStuff) self.params = None self.api = TorrentTvApi(config.email, config.password, config.sessiontimeout, config.zoneid) - + def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Handling request') @@ -88,9 +88,10 @@ def handle(self, connection, headers_only=False): break if stream_type == 'torrent': - stream_url = re.sub('^(http.+)$', - lambda match: '/torrent/' + urllib2.quote(match.group(0), '') + '/stream.mp4', - stream) + stream_url = re.sub('^(http.+)$', + lambda match: '/torrent/' + urllib2.quote(match.group(0), '') + '/stream.mp4', + stream) + elif stream_type == 'contentid': stream_url = re.sub('^([0-9a-f]{40})', lambda match: '/pid/' + urllib2.quote(match.group(0), '') + '/stream.mp4', @@ -284,9 +285,9 @@ def handle(self, connection, headers_only=False): stream_type, stream = self.api.archive_stream_source(record_id) if stream_type == 'torrent': - stream_url = re.sub('^(http.+)$', - lambda match: '/torrent/' + urllib2.quote(match.group(0), '') + '/stream.mp4', - stream) + stream_url = re.sub('^(http.+)$', + lambda match: '/torrent/' + urllib2.quote(match.group(0), '') + '/stream.mp4', + stream) elif stream_type == 'contentid': stream_url = re.sub('^([0-9a-f]{40})', lambda match: '/pid/' + urllib2.quote(match.group(0), '') + '/stream.mp4', diff --git a/plugins/torrenttelik_plugin.py b/plugins/torrenttelik_plugin.py index ada75e9..8f893cb 100644 --- a/plugins/torrenttelik_plugin.py +++ b/plugins/torrenttelik_plugin.py @@ -6,10 +6,12 @@ http://ip:port/torrent-telik/?type=mob_ttv = torrent-tv mobile playlist http://ip:port/torrent-telik/?type=allfon = allfon playlist ''' + import json import logging -import urllib2 import urlparse +import requests +import time from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator import config.torrenttelik as config @@ -19,17 +21,27 @@ class Torrenttelik(AceProxyPlugin): handlers = ('torrent-telik', ) - logger = logging.getLogger('plugin_torrenttelik') + logger = logging.getLogger('Plugin_Torrenttelik') playlist = None + playlisttime = None def downloadPlaylist(self, url): try: - req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"}) - Torrenttelik.playlist = urllib2.urlopen(req, timeout=10).read() - Torrenttelik.playlist = Torrenttelik.playlist.split('\xef\xbb\xbf')[1] # garbage at the beginning - Torrenttelik.playlist = Torrenttelik.playlist.replace(',\r\n]}', '\r\n]}') # excess comma at the end + Torrenttelik.logger.debug('Trying to download Torrent-telik playlist') + self.headers = {'User-Agent' : "Magic Browser", + 'Accept-Encoding': 'gzip'} + if config.useproxy: + r = requests.get(url, headers=self.headers, proxies=config.proxies, timeout=30) + else: + r = requests.get(url, headers=self.headers, timeout=10) + Torrenttelik.playlist = r.content + Torrenttelik.playlisttime = int(time.time()) + Torrenttelik.logger.debug('Torrent-telik playlist ' + r.url + ' downloaded !') + + Torrenttelik.playlist = Torrenttelik.playlist.split('\xef\xbb\xbf')[1] + Torrenttelik.playlist = Torrenttelik.playlist.replace(',\r\n]}', '\r\n]}') except: - Torrenttelik.logger.error("Can't download playlist!") + Torrenttelik.logger.error("Can't download Torrent-telik playlist!") return False return True @@ -41,7 +53,7 @@ def handle(self, connection, headers_only=False): connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') connection.end_headers() - + if headers_only: return @@ -57,9 +69,11 @@ def handle(self, connection, headers_only=False): elif list_type.startswith('allfon'): url = config.url_allfon - if not self.downloadPlaylist(url): - connection.dieWithError() - return + # 30 minutes cache + if not Torrenttelik.playlist or (int(time.time()) - Torrenttelik.playlisttime > 30 * 60): + if not self.downloadPlaylist(url): + connection.dieWithError() + return # Un-JSON channel list try: @@ -87,12 +101,12 @@ def handle(self, connection, headers_only=False): channel['group'] = channel.get('cat', '') playlistgen.addItem(channel) - Torrenttelik.logger.debug('Exporting') - header = '#EXTM3U url-tvg="%s" tvg-shift=%d\n' %(config.tvgurl, config.tvgshift) + Torrenttelik.logger.debug('Torrent-telik playlist created') + header = '#EXTM3U url-tvg="%s" tvg-shift=%d deinterlace=1 m3uautoload=1 cache=1000\n' %(config.tvgurl, config.tvgshift) exported = playlistgen.exportm3u(hostport, header=header, add_ts=add_ts, fmt=self.getparam('fmt')) - exported = exported.encode('utf-8') + exported = exported.encode('utf-8') connection.wfile.write(exported) - + def getparam(self, key): if key in self.params: return self.params[key][0] diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 4efb53e..99b42da 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -4,33 +4,32 @@ ''' import re import logging -import urllib2 import time import gevent import threading import urlparse import md5 import traceback -import gzip -from StringIO import StringIO +import urllib2 +import requests from modules.PluginInterface import AceProxyPlugin from modules.PlaylistGenerator import PlaylistGenerator import config.torrenttv as config import config.p2pproxy as p2pconfig from torrenttv_api import TorrentTvApi - class Torrenttv(AceProxyPlugin): # ttvplaylist handler is obsolete - handlers = ('torrenttv', 'ttvplaylist') + handlers = ('torrenttv', 'ttvplaylist',) def __init__(self, AceConfig, AceStuff): - self.logger = logging.getLogger('plugin_torrenttv') + self.logger = logging.getLogger('Plugin_TorrentTV') self.lock = threading.Lock() self.channels = None self.playlist = None self.playlisttime = None + self.etag = None self.logomap = config.logomap self.updatelogos = p2pconfig.email != 're.place@me' and p2pconfig.password != 'ReplaceMe' @@ -46,29 +45,17 @@ def playlistTimedDownloader(self): def downloadPlaylist(self): try: - self.logger.debug('Trying to download playlist') - req = urllib2.Request(config.url, headers={'User-Agent' : "Magic Browser"}) - req.add_header('Accept-encoding' , 'gzip') - response = urllib2.urlopen(req, timeout=15) - - origin = '' - - if response.info().get('Content-Encoding') == 'gzip': - # read the encoded response into a buffer - buffer = StringIO(response.read()) - # gzip decode the response - f = gzip.GzipFile(fileobj=buffer) - # store the result - origin = f.read() - # close the buffer - buffer.close() - # else if the response isn't gzip-encoded - self.logger.debug('Playlist downloaded using gzip compression') + self.logger.debug('Trying to download TTV playlist') + self.headers = {'User-Agent' : "Magic Browser", + 'Accept-Encoding': 'gzip'} + if config.useproxy: + r = requests.get(config.url, headers=self.headers, proxies=config.proxies, timeout=30) else: - # store the result - origin = response.read() - self.logger.debug('Playlist downloaded') - + r = requests.get(config.url, headers=self.headers, timeout=10) + + origin = r.text.encode('UTF-8') + self.logger.debug('TTV playlist ' + r.url + ' downloaded !') + matches = re.finditer(r',(?P\S.+) \((?P.+)\)[\r\n]+(?P[^\r\n]+)?', origin, re.MULTILINE) self.playlisttime = int(time.time()) self.playlist = PlaylistGenerator() @@ -89,13 +76,13 @@ def downloadPlaylist(self): itemdict['url'] = urllib2.quote(encname, '') + '.mp4' self.playlist.addItem(itemdict) m.update(encname) - + self.etag = '"' + m.hexdigest() + '"' except: - self.logger.error("Can't download playlist!") + self.logger.error("Can't download TTV playlist!") self.logger.error(traceback.format_exc()) return False - + if self.updatelogos: try: api = TorrentTvApi(p2pconfig.email, p2pconfig.password, p2pconfig.sessiontimeout, p2pconfig.zoneid) @@ -117,25 +104,25 @@ def downloadPlaylist(self): def handle(self, connection, headers_only=False): play = False - + with self.lock: - + # 30 minutes cache if not self.playlist or (int(time.time()) - self.playlisttime > 30 * 60): if not self.downloadPlaylist(): connection.dieWithError() return - + url = urlparse.urlparse(connection.path) path = url.path[0:-1] if url.path.endswith('/') else url.path params = urlparse.parse_qs(url.query) fmt = params['fmt'][0] if params.has_key('fmt') else None - + if path.startswith('/torrenttv/channel/'): if not path.endswith('.mp4'): connection.dieWithError(404, 'Invalid path: ' + path, logging.DEBUG) return - + name = urllib2.unquote(path[19:-4]).decode('UTF8') url = self.channels.get(name) if not url: @@ -160,16 +147,16 @@ def handle(self, connection, headers_only=False): hostport = connection.headers['Host'] path = '' if len(self.channels) == 0 else '/torrenttv/channel' add_ts = True if path.endswith('/ts') else False - header = '#EXTM3U url-tvg="%s" tvg-shift=%d deinterlace=auto\n' % (config.tvgurl, config.tvgshift) + header = '#EXTM3U url-tvg="%s" tvg-shift=%d deinterlace=1 m3uautoload=1 cache=1000\n' % (config.tvgurl, config.tvgshift) exported = self.playlist.exportm3u(hostport, path, add_ts=add_ts, header=header, fmt=fmt) - + connection.send_response(200) connection.send_header('Content-Type', 'application/x-mpegurl') connection.send_header('ETag', self.etag) connection.send_header('Content-Length', str(len(exported))) connection.send_header('Connection', 'close') connection.end_headers() - + if play: connection.handleRequest(headers_only, name, config.logomap.get(name), fmt=fmt) elif not headers_only: From 2561431d28f0b8455a156d7423b305491f1d1b0a Mon Sep 17 00:00:00 2001 From: pepsik-kiev Date: Thu, 12 Oct 2017 23:50:49 +0300 Subject: [PATCH 95/95] Changes & additions (#29) * Add changes to archive play for VIP users * small changes for correct work if cid not found * Back all changes * Add Proxy support for playlists requests Need to install python requests !!! For example you can install tor browser and add in torrc SOCKSPort 9050 -> proxies = {'http' : 'socks5://127.0.0.1:9050', 'https' : 'socks5://127.0.0.1:9050'} If your http-proxy need authentification -> proxies = { 'https' : 'https://user:password@ip:port' } * Add Proxy support for playlists requests Need to install python requests !!! For example you can install tor browser and add in torrc SOCKSPort 9050 -> proxies = {'http' : 'socks5://127.0.0.1:9050', 'https' : 'socks5://127.0.0.1:9050'} If your http-proxy need authentification -> proxies = { 'https' : 'https://user:password@ip:port' } * View torrent files from the specified folder Usage http:/:/films * View torrent files from the specified folder Config file * Add exception for torrent-file get info error * Minor changes for hls translations & archives * Add exceptions * Add client info for local IP address * del locale * correction playing for vlcuse=true * minor changes * "at the same time" viewing ability for vlcuse=True * fixing videodelay * Changes for vlc cmd line * Update logobase * Changes to prevent TTV account ban * More readable output log format * Exclude the special characters from m3u names * minor changing --- aceconfig.py | 8 +- acehttp.py | 28 +- plugins/config/torrentfilms.py | 9 + plugins/config/torrenttv.py | 615 ++++++++++++++------------------- plugins/stat_plugin.py | 129 +++---- plugins/torrentfilms_plugin.py | 88 +++++ plugins/torrenttv_plugin.py | 2 + vlcclient/vlcclient.py | 2 +- 8 files changed, 444 insertions(+), 437 deletions(-) create mode 100644 plugins/config/torrentfilms.py create mode 100644 plugins/torrentfilms_plugin.py diff --git a/aceconfig.py b/aceconfig.py index 659ac1b..ff8882b 100644 --- a/aceconfig.py +++ b/aceconfig.py @@ -93,10 +93,10 @@ class AceConfig(acedefconfig.AceDefConfig): # to point ace_player.exe, not vlc.exe!!! vlcuseaceplayer = False # Spawn VLC automaticaly - vlcspawn = False + vlcspawn = True # VLC cmd line (use `--file-logging --logfile=filepath` to write log) # Please use the full path to executable for Windows, for example - C:\\Program Files\\VideoLAN\\VLC\\vlc.exe - vlccmd = "vlc -I telnet --clock-jitter -1 --network-caching -1 --sout-mux-caching 2000 --telnet-password admin --telnet-port 4212" + vlccmd = 'vlc -I telnet --clock-jitter=0 --clock-synchro=0 --telnet-password admin --telnet-port 4212' # VLC spawn timeout # Adjust this if you get error 'Cannot spawn VLC!' vlcspawntimeout = 5 @@ -118,7 +118,7 @@ class AceConfig(acedefconfig.AceDefConfig): # ffmpeg{mux=NAME} (i.e. ffmpeg{mux=mpegts}) # VLC's ts muxer sometimes can work badly, but that's the best choice for # now. - vlcmux = 'ts' + vlcmux = 'ts{use-key-frames}' # Force ffmpeg INPUT demuxer in VLC. Sometimes can help. vlcforceffmpeg = False # Stream start delay for dumb players (in seconds) @@ -202,7 +202,7 @@ class AceConfig(acedefconfig.AceDefConfig): # Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) loglevel = logging.DEBUG # Log message format - logfmt = '%(asctime)s %(levelname)s %(threadName)s %(filename)s:%(lineno)d %(name)s| %(message)s' + logfmt = '%(filename)-20s [LINE:%(lineno)-4s]# %(levelname)-8s [%(asctime)s] %(message)s' # Log date format logdatefmt='%d.%m %H:%M:%S' # Full path to a log file diff --git a/acehttp.py b/acehttp.py index 4f8a453..84e5d07 100755 --- a/acehttp.py +++ b/acehttp.py @@ -116,12 +116,12 @@ def proxyReadWrite(self): # Wait for PlayEvent if videoobey is enabled. Not for VLC self.client.ace.getPlayEvent() - if AceConfig.videoobey and AceConfig.vlcuse: - # For VLC - # Waiting 0.5 seconds. If timeout exceeded (and the Play event - # flag is not set), pause the stream if AceEngine says so and - # we should obey it. - # A bit ugly, huh? + if AceConfig.vlcuse: + # Ignor videoobey settings when use VLC. For VLC + # waiting 0.5 seconds. If timeout exceeded (and the Play event + # flag is not set), pause the stream if AceEngine says so and + # we should obey it. + # A bit ugly, huh? self.streamstate = self.client.ace.getPlayEvent(0.5) if self.streamstate and not self.vlcstate: AceStuff.vlcclient.playBroadcast(self.vlcid) @@ -285,8 +285,8 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No contentid = self.getCid(self.reqtype, self.path_unquoted) cid = contentid if contentid else self.path_unquoted logger.debug("CID: " + cid) - self.client = Client(cid, self, channelName, channelIcon) - self.vlcid = urllib2.quote(cid, '') + self.vlcid = urllib2.quote(cid, '') + self.client = Client(cid, self, channelName, channelIcon) shouldStart = AceStuff.clientcounter.add(cid, self.client) == 1 try: @@ -319,17 +319,17 @@ def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=No self.vlcprefix = 'http/ffmpeg://' else: self.vlcprefix = '' + AceStuff.vlcclient.startBroadcast( + self.vlcid, self.vlcprefix + self.url, AceConfig.vlcmux, AceConfig.vlcpreaccess) + # Sleep a bit, because sometimes VLC doesn't open port in time + gevent.sleep(0.5) + if not AceConfig.vlcuse: self.client.ace.pause() # Sleeping videodelay gevent.sleep(AceConfig.videodelay) - self.client.ace.play() - AceStuff.vlcclient.startBroadcast( - self.vlcid, self.vlcprefix + self.url, AceConfig.vlcmux, AceConfig.vlcpreaccess) - # Sleep a bit, because sometimes VLC doesn't open port in - # time - gevent.sleep(0.5) + self.client.ace.play() self.hanggreenlet = gevent.spawn(self.hangDetector) logger.debug("hangDetector spawned") diff --git a/plugins/config/torrentfilms.py b/plugins/config/torrentfilms.py new file mode 100644 index 0000000..a13b04d --- /dev/null +++ b/plugins/config/torrentfilms.py @@ -0,0 +1,9 @@ +''' +Torrent Films Playlist Plugin configuration file +(C) Pepsik +''' + +# Insert your path to *.torrent files here +# In *nix based systems use '/path1/path2/path3' in windows 'C:\\path1\\path2\\path3' +directory = '' + diff --git a/plugins/config/torrenttv.py b/plugins/config/torrenttv.py index e86d0e4..01dbe8b 100644 --- a/plugins/config/torrenttv.py +++ b/plugins/config/torrenttv.py @@ -32,147 +32,136 @@ u'0x0 Fireplace HD': logobase + 'H1VboxDJC7sE7x3nKXoYT0X5r4LIqD.png', u'0x0 Music HD': logobase + 'hFj4tnC5uqAgpod3doHnJGZxgZXaiP.png', u'1 HD': logobase + 'FtLnmUwjG18XJFEKYvLKjwUq1gwHVZ.png', - u'1 Мистический': logobase + '6NZHK1Lz0SbqVMhy20L9gPEJjW89mQ.png', + u'1 MUSIC CHANNEL': logobase + '45atLSHAjViMISJinK8A0jQA34I0Fi.png', + u'1 tv Georgia': logobase + 'geM35mbIzNqzz3LBvxnViw2EviT2BW.png', u'1+1': logobase + 'omm2Xc8xSVIT6Od6ca4QqMrEXw3jaK.png', - u'10 канал (Израиль)': logobase + 'bkVtuqhnGlL4Ykxlha255ief1YRIDJ.png', + u'1+1 International': logobase + '6fImIbJqJsF3pJQi2UuYRwtRyHMeaB.png', u'100% News': logobase + '9yEWvPmTcFS8lyQ5NjJ7vbYOa3bx1W.png', u'112 Украина': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'112 Украина HD': logobase + '0AUjU7DOzZcpizC4UR19vDZqqthSe0.png', u'12 Канал (Омск)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'2 канал (Израиль)': logobase + 'CSw0eG13VxmrfPXWwUOHyerOhd53UC.png', u'2+2': logobase + 'XHXBC3ghvhh100BNXylSgJLx5FVQgD.png', - u'22 канал (Израиль)': logobase + '6VuqNvDh7nyHsxPolqLr7YpfYLkTNW.png', - u'24 TV': logobase + 'QkclX0M9kXSGGDRhBw8H0T37VDzwNb.png', - u'24 Док': logobase + 'H1UXBai10DjYfScfv1sNAILV9EPDer.png', u'24 Украина': logobase + 'XfKEdfsy4S1zbE8n2tB1tNNe9IkrRP.png', + u'24 Украина HD !': logobase + 'fndLsRJ2T4AFEYqVxMkvz8teE2aTuw.png', + u'26 Регион HD (Ставрополь)': logobase + 'WdXmMmnehWo0vdzVwA4z1tGgRjFTWt.png', u'2x2': logobase + 'hTpJUV15GSTxZ5kJQGLcn42kCzKyEH.png', u'2x2 (+2)': logobase + 'ZPOhZle6vrDaulo2KMmyrCkkkLn7Ci.png', - u'2x2 (+5)': logobase + 'WMlvhNmhzmk2JgKct8Oa5FcWHmR7fZ.png', - u'31 канал': logobase + '69d16xSeYrnw4OAO0cSWY9stdLp5cd.png', - u'360 Tune Box': logobase + 'WofrwD5WhK8TxQvbTBchuo1QKcbbFS.png', u'360 градусов': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', u'360 градусов HD': logobase + 'K4tiqb5ajIgmxqh8X5moJuDjN7q5hx.png', + u'360 Новости HD': logobase + 'K7RGdQQaKY78l29J4Xfgk4F0t9a2jq.png', u'365 Дней': logobase + '0IZrVwoxtmjtgnWu5Dj4Hb8FRc8NIX.png', - u'3S TV': logobase + 'AL7uQOgsyYqE7V1BagxC2jqQVOvucp.png', - u'43 Канал HD': logobase + '1mNoU1QXmcmK48eVTlnADtsEBmA2sN.png', - u'47 канал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'49 канал (Новосибирск)': logobase + 'WK3XEVPHqpV3VkKEBHi8QSXoPKPj6T.png', + u'41 канал Домашний (+2)': logobase + '6vBXDKono9On9ND4WG49IeaXzdjWcX.png', + u'43 Канал HD (Туапсе)': logobase + '1mNoU1QXmcmK48eVTlnADtsEBmA2sN.png', + u'47 канал': logobase + '8aHwrc5KVFI4FXwgPbBf5vjBSIQ52B.png', + u'49 канал (Новосибирск)': logobase + 'liRZcIcNbzQTngm8CIkcnw7gLJfeIJ.png', + u'4Fun TV': logobase + 'MBRDVkacgvAn0zw4ceuI1ji9LxJFsb.png', + u'5 канал (Киргизия)': logobase + 'ftgWX6P9yWch8a2fF7qH7QdmhtWPu8.png', u'5 канал (Украина)': logobase + '9La0uS6S8rMKr0BOh6vSCQLNiCqN7N.png', + u'66 канал (Израиль)': logobase + 'ZNDGDdjbJMIB3owCIKosKGsUFe2En3.png', + u'78 канал': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', + u'78 канал HD': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', + u'7ТВ': logobase + 'vNvqlbSnA9yjRCEdcGV773QxysaOxm.png', u'8 канал': logobase + 'wKPUJjcpZIfI5zBSZwNayOlv63zRNV.png', - u'8 канал (+3)': logobase + 'XaNnxrpV00f3Zcfyw6WAg6uMT8NznG.png', u'8 канал (Красноярск)': logobase + 'wKPUJjcpZIfI5zBSZwNayOlv63zRNV.png', u'9 волна': logobase + '8zDeTSBhsmJDbXo9dxHA1c9mDOP9sP.png', - u'9 канал (Израиль)': logobase + 'DLEdAyl9qChDL6eDpI5HhVaArJZQHd.png', - u'9 канал HD (Израиль)': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', - u'A-One UA': logobase + '8rEKNCXhKeWnUvGhWCsrb2RN5FMqJi.png', - u'ABC TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Akudji HD': logobase + 'V7sRTl5PvWvFg45engRn1p6Zb7rB1e.png', - u'Al Jazeera English': logobase + 'B1AC3jl0CY8u4qxO0aIEHVMFQFvBUu.png', + u'9 канал (Израиль)': logobase + 'dsi2SD7Pmq3sxxaNPpRAUgKmxXh4s6.png', + u'9 канал (Рязань)': logobase + 'bO1hIXjoA3KytchKgshG1Bhzcz2DNT.png', + u'Abu Dhabi TV HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Al Jazeera English': logobase + '5ZfDU3kFx7mh5ntH0QZfagkjIWbZ4C.png', + u'Al Rayyan 2 HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Almaty HD': logobase + 'DKYJ9oBuRE2JmxrZNo4i4vM9s8dsUb.png', u'AMC': logobase + 'rozrC8ZPYA1YGajyow35doeVcaQzpa.png', u'Amedia 1': logobase + 'jT3vAEOG5jTd2t8GcC797Bw5W0kSl9.png', - u'Amedia 1 HD': logobase + 'KPN6ULwGzTNbKziTI9sN7xfFPhtRzc.png', u'Amedia 2': logobase + 'fAvxTQbWu0DAcMkqej0m73KohAcQJw.png', - u'Amedia Hit': logobase + '3lKypp7zXsJ3FDusXzKw9hHnVLythX.png', + u'Amedia Hit': logobase + 'HdnTfcZCgP7Odm1cOKNq9j4yJDRiFP.png', u'Amedia Hit HD': logobase + 'HdnTfcZCgP7Odm1cOKNq9j4yJDRiFP.png', u'Amedia Premium': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Amedia Premium HD': logobase + 'ornzQpk6WCW6xk0lyBhlwqH8u2QyU7.png', u'Ani': logobase + 'vui1cRrE05CZv1N9Qb20jJ6mTFOJue.png', - u'Animal Family HD': logobase + '58VN85gBIna4Ko2K2uNeG6ZrfFNTLK.png', + u'Animal Family HD': logobase + '6Xrlt9W9PBeF99h1ayRh8zteAheFUQ.png', u'Animal Planet': logobase + '45.png', - u'Animal Planet': logobase + '45.png', - u'Animаl Planеt HD': logobase + '9HZGan5rQItVQOfnB91FGqyJXjoqYV.png', - u'Anyday 3D': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', - u'Anyday HD': logobase + 'r8hT8FoPXteOKe9H8FjQTdIttfOg8S.png', + u'Animal Planet HD': logobase + '9HZGan5rQItVQOfnB91FGqyJXjoqYV.png', + u'Anyday 3D': logobase + 'Q9gPrtyM73PzW5Jlf0pXnjCJcT0Tq7.png', + u'Anyday HD': logobase + 'nZ0Aa5bcpThFFTGvBUfW65GTgVoH6c.png', + u'Ararat TV': logobase + 'HdJCAim5FV0bpwYQ3Fam5yxoMzFxAe.png', u'Arirang TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Armenia TV Satellite': logobase + 'M4UAMT6cEyL3zuim9fzy38vZRkIfKq.png', + u'Art-108 HD': logobase + 'RXU5Txc5ki40dg6pJUleIMIabCcltD.png', + u'Astana TV': logobase + 'R09FzjK0PWCE06kTL9obz4RpJ9SUl5.png', u'ATR': logobase + 'uggqxQxxDTuz1P1mY4PZdlRcAKow3F.png', + u'ATV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'ATV Azerbaycan': logobase + 'yx7GzFSH3BlxYYa0Nuq4eiVRqxZSa5.png', + u'Aurora HD': logobase + 'HYtru2e2Sh6VrZsnNxdTyNfO8m9eoM.png', u'Az TV': logobase + 'FB2qxgNm8xTU6YiR2fY1jH4PIRR2SF.png', - u'B4U Music UK': logobase + 'LlfDsq3FEU2D4P3lmj25fvl6zQnvNM.png', + u'Babestation': logobase + 'DbvHMminWOobAbD3IliRECsG8RH6uL.png', + u'Baby TV !': logobase + 'aE2CDNS2w6p5LA4OThfOWOzYT7jQno.png', u'BBC World News': logobase + '8cSYWwvq6BAzcGDjJODvCGQYGJ2c4S.png', - u'Beatz TV HD': logobase + 'kXJ08AkdOdRbaYON4Tsmkgk14zqifk.png', u'Bloomberg': logobase + 'OTsoNZRT8xjXz5nnTICPCQRvNLjBal.png', - u'Blue Hustler': logobase + 'dFDAUzpGhQFOyHz59iyi7gsGHHMQbj.png', u'Bollywood HD': logobase + 'HXzWxtMxmXkrpgr88Kh8Z1A8F6o5dK.png', + u'BOLT HD': logobase + 'EEZrUgR0IgyOp97DI8sGPT78J40Doq.png', u'Boomerang': logobase + 'tsP2U3zkp5o8B0TD6luRLg0leS9FvM.png', u'Boutique TV': logobase + 'bDSWVUfAEYN9VS25roxW52xO1pj6qp.png', u'Brazzers TV': logobase + 'VPCmFsMfpnB0pg9VBzvUG5TkRcHTsR.png', u'Brazzers TV Europe': logobase + 'OmVBp3Kz4Lx722dq2e1OxE26QSxrDA.png', - u'Bridge HD': logobase + 'hSVhfaLPt58KBflQjQxwiDqqO6h3rm.png', + u'Bridge HD': logobase + 'P7JEo7cGv1dtk6X69SkfEQeiVrQvXj.png', u'Bridge TV': logobase + 'dPhBiaViIznwjeWU3pTbIXFmS3iJkU.png', - u'Bridge TV Classic': logobase + 'DsJRpcbI6rgjONbQftC5nHt1XMAXYQ.png', + u'Bridge TV Classic': logobase + 'NCuKRKNU2rwc8DkpF555fINSz5xaIc.png', u'Bridge TV Dance': logobase + 'ofmgiZlRmwOZtTaYtUtfErZS3ODDDM.png', u'Brodilo TV HD': logobase + '0pLPWiGqZjDe2O4vbPd8QL0qGsA9lq.png', - u'BT Sport 1': logobase + 'whrGkbmFh1pcdxuanV5u4zcD7PDuhd.png', + u'BT Sport 1': logobase + 'RNwudSF1Lys88WmIXhiS43g2urfknl.png', u'Business': logobase + 'wvwXc2x8Bjev90GD6LO6t7TL2aKW1h.png', - u'C More Tennis': logobase + 'ql4qy7gHN4GtWhcqfd97HqXUs8HXI9.png', + u'C Music TV HD': logobase + 'YhtkJhsV8NKwm4FGWhQZDmYcW0TYQS.png', u'Canal 3': logobase + 'rb39jMz38xLuXKGCuWD2GcHHMEzfX1.png', - u'Candy': logobase + 'YW8BJnImniuR7l1U85Khw31mX2XO0S.png', u'Candy HD': logobase + 'YW8BJnImniuR7l1U85Khw31mX2XO0S.png', - u'Canal+ Sport HD (France)': logobase + 'I4vGNtOsYtkBnwOwFi94RHazK1ngqw.png', + u'Candyman HD': logobase + '6fgWPM2UHUiHic7UVLG8YCVq5bC7uw.png', u'Cartoon Network': logobase + 'NTNQLLri3Hh9iqYjW7VEkFYJsTLjk9.png', - u'CBC': logobase + 'GuGWslWHNQ2fb88q6JYKSAV1n9A6hF.png', - u'CBS Drama': logobase + '1Mcq9jbl7qPeXpT0bbPTnf8aM3f7dq.png', + u'CBC Sport HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'CBC TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'CBS Reality': logobase + 'QbWaD2vNgLRLY0Hsccg3nf9azAXRld.png', - u'CCTV 9 Documentary': logobase + 'dqoArqO8dd09vzsSSQPqLUj5wM9O9M.png', - u'CCTV News': logobase + 'TyWVFvKwV3lOYYXWVJSj8HMu5AodJr.png', - u'CCTV Русский': logobase + 'DcaE92lKRSDFBPlYUnsNRauFLbotKu.png', + u'CCTV 4': logobase + 'PsrMNDhKrEgCJA5lAR9UUtBlOI4uJg.png', u'CentoXCento TV': logobase + 'dPuPlqJtkLMRy7NmOhyHDfL0PJqYj4.png', u'CGTN': logobase + 'TyWVFvKwV3lOYYXWVJSj8HMu5AodJr.png', u'CGTN Documentary': logobase + 'dqoArqO8dd09vzsSSQPqLUj5wM9O9M.png', u'CGTN Русский': logobase + 'DcaE92lKRSDFBPlYUnsNRauFLbotKu.png', - u'CNL': logobase + 'wwQ6u4lFXyaUDJLu2JOh6mtbkIz0Nf.png', + u'Cinema': logobase + 'beyfqyeacrFG0PrOeKUQhzQ4bV6Q5d.png', u'CNN International': logobase + 'lIwbRbM3ve4ixhFXp75Oap9nzcO71G.png', u'Da Vinci Learning': logobase + 'Yl6p1IDDkZxxiUa3p2JxI66mIlOPns.png', - u'Dange TV': logobase + 'ofmgiZlRmwOZtTaYtUtfErZS3ODDDM.png', - u'Deluxe Music': logobase + '3VnQoAyJh3RZM88USPszL1TZIE5t0u.png', u'Deutsche Welle': logobase + 'RnqBDfde1HP4OkZhXWlKB1xkHi6Io9.png', - u'Digi Sport 1': logobase + 'gk0ajzoQjMHlBKM298kWjcfNy1P8ec.png', - u'Digi Sport 2': logobase + 'x1HMd7tDoprxuZvq90Uj2Gb1eEOhTm.png', - u'Discovery Channel': logobase + '44pYdn5EiVvHLg0VtshNaBO6HBUosF.png', + u'Diema Sport 2': logobase + '4xUpzBxK0sL6Hc9KQADA1LhmFbE9Yi.png', u'Discovery Channel': logobase + 'oKx1ImWVRT3AK3DHYWUVc71JZUkwu5.png', u'Discovery Channel HD': logobase + 'SmWnYlOvkJn8GzttT2UY0vmo8PYfMg.png', - u'Discovery HD Showcase': logobase + 'HCjNT859EyclaIl0GSueEiUacnR5Qd.png', u'Discovery Science': logobase + 'GAaO3EfDwMuHAIelG4gYW6TDEYbLnS.png', - u'Discovery Science HD': logobase + 'nnC8c8Z3iWbGSsgDORtWrS6O5n0ljr.png', - u'Discovery Showcase HD': logobase + 'CWSMBfG9Cjz73B4r9MJWMCsstIBZcY.png', + u'Discovery Science HD': logobase + '5rlWsKfvt9jYnUht7vXqEqA47Y05wh.png', + u'Discovery World !': logobase + 'dMUIABPd7wmzLHryJ0IjVKRvWSZXkS.png', u'Disney Channel': logobase + 'JxEjTeXwExjnxutQGKJBmMI85tpNqK.png', - u'Disney Channel (+7)': logobase + 'LZQKp6qoXQ8Ef64qUzcCOACy8qJAoq.png', - u'Disney Channel (+2)': logobase + 'awk1MeZ3IP1HOAtPt4jfvdR2P6YqIc.png', - u'Djing Animation HD': logobase + 'ZA8pwcclRpzTflNKLqeZmDHCA7lHoX.png', - u'Djing Classic HD': logobase + 'fDCLK7k2nYRKPAD125JdHM5uuEM7bZ.png', - u'Djing Ibiza HD': logobase + 'aGqPHXL6MxB8j4GXldadnOR3r5D03f.png', - u'Djing Underground HD': logobase + 'ceUcUA6hsEV7tBVmSF1chE203B14M3.png', - u'Dobro TV': logobase + 'tcHbtjkY9gYD4VqipgwLP2BYxgdrZb.png', - u'DocuBox HD': logobase + '3AIG2M0cHQuzeyQA2o1hClKAy1F8Ir.png', + u'DocuBox HD': logobase + '4ZjuKuTHFCuXqKkEmKYGcBFe6UK5z4.png', u'Dog TV': logobase + 'NblfllG2mrqX3Zrb36rv7hLUe2sG1R.png', - u'Dot Dance HD': logobase + 'Z8TDmebVzyNS57me2IO2rd2LaiNRM6.png', - u'Dream TV': logobase + '3k3m3ElnUQ0UhX54qa7hOGbmNjKZSc.png', + u'DREAM PORN HD': logobase + 'rEdYcZis6CYk8QAFJzJRQQE1NPoZGj.png', u'DTX': logobase + 'bx7MosYUikfuwwIOzx2elT6Dh7h50I.png', + u'DTX HD': logobase + 'bx7MosYUikfuwwIOzx2elT6Dh7h50I.png', + u'Dusk TV': logobase + '5j0MFmxPLBN36rJR2yYxgnOOHa23gh.png', u'English Club TV': logobase + 'Hf5RUQ91cEGtHoaK3GK6VIZlJ8Leql.png', u'Enter Film': logobase + '8FPA5SCEIj35fBO8yrULnefW4NzIzk.png', u'Epoque': logobase + 'eULbHv3DyublWF2rAhw38DV63cV3V9.png', + u'EPOQUЕ HD': logobase + 'EKfkYWRSkYiN76MxCZCjawxewjBOYY.png', u'Eska Best Music TV HD': logobase + 'wI3e672FQZpD8yr8aIV8Q2fL15zENv.png', u'Eska OldsCool TV HD': logobase + '7LW3s6C3D3yeciqTY0CKXpEmL3Tb3w.png', u'Eska Party TV HD': logobase + 'MaX9is65hlDpRDwgOikGE2RqMRxn8G.png', u'Eska Rock TV HD': logobase + 'VU2POxFhYCM4XhFAtOp8Y4sdlbu32M.png', - u'Eska TV': logobase + '1KX19DgurgoaSKNTTpjXCeSQr1epwv.png', u'Eska Wawa TV HD': logobase + 'r9d697qox4MFgypnVqeILYWKcfbwNc.png', - u'ETV pluss': logobase + 'cZBJJjYDyuuJHMQk3ZOogcDZYcu2SK.png', - u'Espreso TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'ETV pluss (Эстония)': logobase + 'cZBJJjYDyuuJHMQk3ZOogcDZYcu2SK.png', - u'EU Music': logobase + 'hubnM8JT96ngUDKI6WBYv9aKMIyWvS.png', - u'EU Music HD': logobase + '1sHUUmgTa6QmeeXWeZ2mFfPZyHX4Mz.png', - u'Eureka HD': logobase + '2cuuRl39vFAHg1pjMKWJJoqwYOqo6y.png', + u'ETV': logobase + 'EXXEf0i7yGRDFttKSwBq4CLoGiuxbW.png', + u'ETV2': logobase + 'dR4fj3bOJ4PAqBEKskc9yLNj0unwAD.png', u'EuroNews': logobase + 'Vb3fP5gUK0q40WuzYeUhMT7RQmDg27.png', u'Europa Plus TV': logobase + 'PkatgpdmA4ArsgSG5shu0ZCQQ5RgMx.png', u'Eurosport 1': logobase + 'QA9jgUaQRrE4vMno04eM3aUrklXOce.png', u'Eurosport 1 HD': logobase + 'DpFTzUEA3y67Z6ObTPF4xH0XLNRAZm.png', u'Eurosport 2': logobase + 'qYbdkVFDkhGqTAjXlRtIj2Fg45bmrm.png', u'Eurosport 2 HD': logobase + '0mo5WOW3xn7FmcIRM9PMZxb2Zgad86.png', - u'Eurosport News': logobase + 'fwZZMuQvqp6eMlji1jhFuEn3v85CsJ.png', + u'EvilAngel': logobase + 'FufZ2heFswzvAbRkTQZs8UJBYGsxuG.png', u'EWTN UK and Ireland': logobase + 'maP6bdwOGv77xHPvRnKBHww9cmG2oV.png', - u'Exotica TV': logobase + 'K5hm3mURkkDaSc7RI5RDti5edynMGl.png', u'Extreme Sports': logobase + '21FhIqWK82JDPNuLTEIC9hSO2EHfks.png', - u'FAP TV': logobase + 'd5ulbooZicKw3aMLwn2Ho0yD9c46T2.png', + u'Extreme Sports (резерв)': logobase + 'F4fGoW5QeUXRJnNnfOnhY3ZejkVyPn.png', u'FAP TV 2': logobase + 'YVmUBY8cBPO8IoL4djlyeKCDbs6f0p.png', u'FAP TV 2 HD': logobase + 'KqVXT8GPTPuPEM87hZSqDwajqcBBgY.png', u'FAP TV 3': logobase + 'S5gXPPcF53lFHQ3kgJofKMQudRQKPt.png', @@ -194,77 +183,55 @@ u'Fashion One HD': logobase + 'iPs2ptiBXm8h0KSnRmyqu45texHNig.png', u'Fashion TV': logobase + 'PSjqabjhYIBqcS8hUA8WNrZEjV4zZY.png', u'Fashion TV HD': logobase + 'z6q34MCsDCntK8jbJXp4pBQiPlIiNx.png', - u'FashionBox': logobase + 'bNAHBFlVPUSr7PI5bPQVXQcJiOcjd2.png', - u'Fast and FunBox HD': logobase + 'D88mJMen7WRxrg0C7TFFeiXXI1gRHM.png', - u'FightBox HD': logobase + 'mZbmQtneRV9cWJsMQ2PSfkGqbsJm9o.png', - u'FilmBox': logobase + 'XVIhYgI4sM1faDqfgMxWqbuOew9bn4.png', - u'FilmBox Arthouse': logobase + 'aZIcmFuP4mihAQoNXEK05oglDfhupR.png', + u'FILMUADRAMA': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'FilmUADrama HD': logobase + 'fWGkWZarY98h18LXSQwHmSAZNXhZW4.png', u'Fine Living': logobase + '22I1fK1aMCUgeYUCV0K3vwmlJKELZD.png', + u'Fitcult TV': logobase + 'XIwNf2TL3xUPnLD0L7Y5Q1FUwwpcSp.png', u'Food Network': logobase + 'W68LEGlOOMPtN0qoGXvdkWhHBjKet6.png', u'Fox': logobase + 'XGC77wQNeEyaJ2z2mDipyIPsoF0xc1.png', + u'Fox HD': logobase + 'Pl8S60EJ52htHxi1gAw1SS1y8i1p3z.png', u'Fox Life': logobase + 'rkksGUl3DstQSEyT26Q07hNCEwyNnd.png', + u'Fox Life HD': logobase + 'Vou521VpOGAGqhp4HUfiG7BSbKNSk6.png', u'France 24': logobase + 'pViXgcMjLnB5WyOoQJ7sBxhHo5fTSB.png', - u'French Lover TV': logobase + 'XHZQcVegBeqVMOYDqzr9EruPgbQpX0.png', - u'Fоx HD': logobase + 'Pl8S60EJ52htHxi1gAw1SS1y8i1p3z.png', - u'Fоx Life HD': logobase + 'Vou521VpOGAGqhp4HUfiG7BSbKNSk6.png', - u'GakkuTV': logobase + 'Xf4EuvwQ5YhlZGy1ueGCzZvY6ZW36k.png', u'Galaxy TV': logobase + 'pU2NsRP9CVtEgQuDTi9jcTYV8iAD4a.png', - u'Gamanoid': logobase + 'DxEQkipHmJjSY0wHGKHDV6eAZKiwoG.png', u'Gamanoid HD': logobase + 'DxEQkipHmJjSY0wHGKHDV6eAZKiwoG.png', u'Game Show': logobase + 'Uc9sBHj0DAYzfXMZmdxeriCBZvUpeb.png', - u'Game Show': logobase + 'WgebxxZr1mTHsGtMTYZzLvV3OnPQrQ.png', - u'Game Show-HD': logobase + 'sB7MetJbNWRBt2Hk56gffI5F9hraLt.png', + u'GAME SHOW HD': logobase + 'x8rAmyXyw8Alq0OKwV9d8iB56lVxRn.png', u'Ginger HD': logobase + 'eW7CqWW2bppbpKzhNctyElyv5Nzs30.png', u'Glazella 3D': logobase + '7EM3eHww2EvOf9jgeFPdZrsQwO8Fz7.png', u'Glazella HD': logobase + '7EM3eHww2EvOf9jgeFPdZrsQwO8Fz7.png', u'GlobalStar TV': logobase + 'Y4kNxgbAnPGCt753P73NzCIbcNz5lD.png', - u'Gulli': logobase + '2IynBdmw3mmdXt01r8DXKxeor7STnw.png', + u'Gulli Girl': logobase + '2IynBdmw3mmdXt01r8DXKxeor7STnw.png', + u'H2 HD !': logobase + 'U7FUILutKUn8pF2W5JdMGMvcfrKIIb.png', u'HardLife TV': logobase + 'fHPc5oaRdIzKpHTqBFnHA4O2LVf91D.png', - u'HD Fashion': logobase + 'EU8qD4hyW8aJjCdSYOVNOMk8WP02vw.png', - u'HD Fashion (SD)': logobase + 'nEIXShrZ56g4IA4VKXReUBF32iYwKJ.png', - u'HD Fashion ukr': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'HD Life': logobase + 'jUteUS0xRGdvLyBVqNjowEUDkOjT0t.png', u'HD Media': logobase + 'woAI3zcytfbyiX0LBRToKzErJNy1qF.png', u'HD Media 3D': logobase + 'takNty6pirY7TeCRPX5VyUW1mZHHxI.png', - u'Hello TV HD': logobase + 'o4WXPUTr5f2tuXFdjuLqgEg3qieGre.png', u'History Channel': logobase + '9cVifexiWW0qWDhhpnLNVydoZkeRqZ.png', u'History Channel HD': logobase + 'VFgU260pmiIyPxCzD3f8R7Yc6DXClH.png', u'HiT Music Channel': logobase + 'n7tghbIGAAh8cyWe7Li0E2iNEJSqkl.png', u'Hit TV': logobase + 'FnCnW1vmvm8gjAwMGXe2Q8qtN1C3zy.png', - u'Hit TV (Казахстан)': logobase + 'jTaWMihq6ZttbxHba4BweWDLywOHpi.png', + u'HIТV HD': logobase + 'rx6sjyAVpjkYDNPEwX5XksWyxx5mVg.png', u'Hustler HD': logobase + 'LBz8ia8AASewVuLjMs5v4MDiVYfsJO.png', u'Hustler TV': logobase + 'wgAR4TI1xdd2LAtnbkpvFAfnnn5Uru.png', + u'i24 news (Израиль)': logobase + 'eIIDTFtWAOPOT8x5p5ICSucpe8JBZo.png', u'Ictimai TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'iConcerts HD': logobase + 'fLNDK6Nxz7xMv61nOsZvED749DlOtz.png', - u'Ictimai': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'ICTV': logobase + 'YuNtYxhj9vqgTU9kVuz9imhqviY4PZ.png', u'ID Xtra': logobase + 'fq3ovkZeu4R4YVYt1VMZN1NU7mMrH9.png', u'Idman TV': logobase + 'UhhUZdtM7FjDFbmoleWalj7tp6nj5o.png', u'Iland': logobase + 'S2eR34Z8l9ne1lteBOLvknOq64jMJN.png', u'InterAz': logobase + 'rdntbmpYEYtyRbW3nGa1FUNZPXF53O.png', u'Islam Channel': logobase + 'XqP875iZVDQXcyNB4AnAEZ4n3PX9xs.png', - u'Jahonnamo': logobase + 'OqNuJdvRTdBh5NeydnpmCfMnTJs9XZ.png', u'Jasmin TV': logobase + 'MBRUFcRx5wtHLZmgahvcGdXB2ERdst.png', u'JimJam': logobase + 'BPDFCK5SQF3mXu5MsDNSdtvz4Gjawo.png', - u'Kazakh TV': logobase + 'il4jEk1Mrw3Miub4nIjzSFNwq7TkaI.png', - u'Kazsport': logobase + 'Hrl1mL3UFqhv8OE7z0wbo4Z0zR6Knc.png', - u'Kral TV': logobase + 'pojhh0tCHuXtk5KkqUl5Nd3JUD3gBB.png', - u'Juce TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Kidzone': logobase + 'CMwzzYODhlLDC188nnF28qxfwvMkWa.png', - u'Kino Space TV HD': logobase + '28A3JkU9kVHQWimTbBinKpqYyjZHcA.png', + u'JuCe TV HD': logobase + 'oDJZW3oCgEFhE5Cvrm7YfcS63iag5x.png', + u'KBS World': logobase + '0bbrB0JSsAxtzY3suHEeph2V6oJGng.png', + u'KBS World HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'KroneHIT TV HD': logobase + 'dq6m0UE9ffgAbXNt49qDlIAr7T8lud.png', - u'Kvartal TV': logobase + 'HeyS7MiMm71GffX6GeOFDMIs2NOgLv.png', - u'Kvartal TV': logobase + 'zkg5FwPBQonqxQlFIOblrboFQpHK6i.png', u'Lale': logobase + '99AZ5VwFy9yZrgsdZ6kIxcoYqwSyUH.png', + u'Leomax': logobase + 'HGm0W3LbxRPeRcgEdYuuBEKp1zJ104.png', u'Lider TV': logobase + 'LByLhdeQ30Ln5pSZRYSpoLpXQS5Ytr.png', - u'Life78': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', - u'Life78 HD': logobase + 'ZloJoTx0yurBDfEtB6V91vRuF8mxB4.png', - u'LifeNews': logobase + 'K8dBCUNz1BSwbgUYYU04i2qJpQKLMc.png', - u'LifeNews HD': logobase + 'Mvurp6cp7Sq2fV3tnFBwPtJy7Ifm1i.png', u'LTV World': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Luxe TV HD': logobase + 'gggiBSZ0905Dkcxtdj8Mh8ie14kpPa.png', - u'Luxury World': logobase + 'QvaV83TwgWHnaGmw4C822MCNYi9zio.png', - u'Luxury World': logobase + 'LB9Pgt6QNYOVDEJqKI2xcmq6VZOP0m.png', u'Lviv TV': logobase + 'VMl4S9yd5IYFAVFaR8XG6KEm5pgVu4.png', u'Maxxi-TV': logobase + 'uqyYr07PPk2OuEdnNrbkMHVlGZCJp5.png', u'MCM Top': logobase + 'b4SLIxXFbKUErZxBXfGoo6eQN5Mu38.png', @@ -272,8 +239,9 @@ u'Mezzo Live HD': logobase + 'lGOtRiUoGyJUMCtAtUwevBerpQORD4.png', u'MGM HD': logobase + 'o4K0lgc2D1GkuFwMC3Cdg1uK6fMrG4.png', u'Mi Lady': logobase + 'Fd556PD2ffD6zdVUiX0dvVfBJavBNd.png', - u'Motorsport': logobase + '7fAmhbXY4MwyRxACHpWPIaNqNiW6Y8.png', - u'MTV AM HD': logobase + '4jFcW4ycRkxdcHqHaVM8J2oIxV7re8.png', + u'Moldova 1': logobase + 'OdWQq0RFoU0GODPHsR94zpTnyFoUX2.png', + u'Moldova 2': logobase + 'V0jtpwewWSjbPJS7KCkIbDXkQA5ne8.png', + u'MTV AM HD': logobase + 'tctwS97YUw7YyfwmeST8rXLHfrQC0v.png', u'MTV Dance': logobase + 'ZXKNRw6Ai8u4lY0wxeycQwj2dIkm66.png', u'MTV Hits': logobase + 'iLJhuLh9kFLQkG4ERvLGSjSgMfNiM4.png', u'MTV Live HD': logobase + 'WjyYXtYHhG5COxGab7luHb1bmvAioA.png', @@ -283,280 +251,247 @@ u'Music Box RU': logobase + 'zaHCW7nPCyGRnqHDkenCIXo7d6vR7v.png', u'Music Box TV': logobase + 'fvt4pris0lwnVhSyUrh8QlyzWBhbgz.png', u'Music Box UA': logobase + '8T7Rnct8q2VHhRm0BcGFxCjxuYEArc.png', - u'Music TV': logobase + 'XQB0y9UMs7XT4YTtKd8AcYigdhP3Wv.png', - u'Mute TV 3D': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', - u'Mute TV HD': logobase + 'hlnSJQ7CeTNjKR9FmOki97DzTTgyLJ.png', - u'Muzzone': logobase + 'eZTTUdsMLYJwwO5ewPcE9mpAmdmSXj.png', - u'My Zen TV HD': logobase + 'GkgzM41JkGOhbS33KepRPoAckZv2Xz.png', - u'MUTV': logobase + 'yDbkEj466sOzY5cCPVbCW6NIT7kkly.png', + u"Music Choice 90's": logobase + 'QN9CRHnBlwKPK555QLsUWwoyGEJmox.png', + u'My Zen TV HD': logobase + 'JkOLtB3RVvI3d2oOC2RSE2ZynIVltv.png', + u'N24 Austria': logobase + 'aQjAnNNoEEtgaFuBFQkBSfh8A5kwlX.png', u'N4 TV': logobase + 'R1NDSrUKnB1RoT8iv1okSe2xY39W4J.png', - u'Nashe Music HD': logobase + 'Xv91mHlQ4d5fCWu7xxpgy847qTWyc2.png', u'Nat Geo Wild': logobase + 'ciHIDUuHEnkEuPghbcqkDQx4vadle3.png', u'Nat Geo Wild HD': logobase + 'YYa1wyNA9prFK1APZ2ZSHGirPpm8kY.png', u'National Geographic': logobase + 'i6STSw6Hg1wWP18yBAOyKoKpSMeKLu.png', u'National Geographic HD': logobase + 'hK1waimMq9eAp0ugM19moSoQvUeve5.png', - u'NBA HD': logobase + 'ZfUh18TUU7KVCgzPPi79D5Zqtu8njC.png', + u'NEBISET TV': logobase + 'bugOHtRHyF9Nx5ba50Eyf547mn8kS1.png', + u'News Network': logobase + 'puoOIFeHu3ojMldKVG7VVObJji589p.png', u'News One': logobase + 'SnaQqBa1YMS2b8b8EGdZSMCConLbDS.png', - u'News One HD': logobase + 'SnaQqBa1YMS2b8b8EGdZSMCConLbDS.png', - u'NewsNetwork': logobase + 'puoOIFeHu3ojMldKVG7VVObJji589p.png', + u'News One HD': logobase + 'jlgF3yZBnmiBKmYCa20SmajQmPqqYQ.png', u'NHK World TV': logobase + 'JJ8Sh9c7zA3PaXlK1ZaNjy3GgWQFh2.png', u'Nick Jr.': logobase + 'D87kJ3fIWIm5wKi5qxm24nbuPQv0U8.png', u'Nickelodeon': logobase + 'j66xpaZbfiYIgQxv76QAPckPVjmLNs.png', u'Nickelodeon HD': logobase + 'CFYx7Bd1aDSgkqjMLLQjFE6xe3u1E0.png', u'NRJ Hits HD': logobase + 'lSwvcnY5XfGEwyrKSvygH73fcYDZKI.png', + u'NTD TV': logobase + 'iy4LZ8PlkstKJ2uuyIELVMuCSz5yEy.png', u'Nuart TV': logobase + 'aqMIuUixqLQYmJITPnOGtFkRPuTqKa.png', + u'NUART TV HD': logobase + '1Ivzqb3yf7X4nk8zQNkCZmW0vthj6g.png', u'O-la-la': logobase + '7ddvb8Tivq7yuEgffrKildHi2BQcCE.png', u'O-TV': logobase + 'UlIt4rE3FZs7IQmPN3tsGS6fqY5F1D.png', u'Ocean-TV': logobase + 'cvBAngU16nJU1bEzxAEcMPiPvf7fVT.png', - u'Olympic Channel HD': logobase + '3rHDtuBvfq5GWQbPhW7hDEcYeabTwO.png', - u'ON TV': logobase + 'W4wsmGmpUkVVAhcBocGkPsRA0o1xTb.png', - u'OSN Movies Action HD': logobase + 'BUJiIaGHNzhRASjqNAgRIoC8Xz2Ket.png', - u'OSN Movies HD': logobase + 'UMzAVPfEGT4NxDvqA2nZrv61IFh4Bs.png', + u'Ocko Expres': logobase + 'xh9xvE50KtXueT6WGZt7QBWZk7y0uL.png', + u'Ocko Gold': logobase + 'ZfRkKa5NkLQ0rxDI0Z4XHqTsJkQs2Y.png', + u'Ocko TV': logobase + 'GRn2qo7rOMBDzPPSBdF1NaWsLPEtbd.png', u'Outdoor Channel': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', - u'Outdoor HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Outdoor Channel HD': logobase + 'SuxFoocUmay5G8jtfJaHL1yIbAT5Hr.png', + u'Ovideo.Ru HD': logobase + 'DecuiVbBOWvLVg2VX3MXmegwxCdowF.png', u'PanArmenian': logobase + 'Goi0hXa9CeThenpkvfPq7E961TMUAT.png', - u'PanArmenian TV': logobase + 'tPxLstRFhFYfGzT8Elg0a1QGGf7wf4.png', u'Paramount Channel': logobase + 'Iyzjhb5BBRKfvEZdBwNJ785jzroODU.png', - u'PARAMOUNT CHANNEL HD': logobase + 'LD6lUAlPHwEUOGeqDfa4xBfCdZY8nC.png', u'Paramount Comedy': logobase + '5EtvAWXB7VK1Yw82yvO28sY28dU4ZC.png', - u'Paramount Comedy HD': logobase + 'xN7JbuSQbfkVaI4fVG4XuwRDjs85Hk.png', + u'Paramount Comedy HD': logobase + 'm8YTKk5S5vVQ1rdmi1lsgTCOQH6lY7.png', u'PassionXXX': logobase + 'U2oO3j1eda31nrTwHqaQ8psrLfH5CD.png', u'Phoenix CNE': logobase + '4A3PI1xIjIeECNSMDDvOfwXd5n2sso.png', u'Phoenix Marie TV': logobase + 'mC9hfoTbTEXlNXEVFrJvXaj78QGUhA.png', u'Pink O TV': logobase + 'aI99kH6bHY5Qt2ph4W1Nh0wxdzd1p8.png', u'Playboy TV': logobase + 'lIWBxmt5GDl9tg4KsQNtA0CuZWdOHH.png', u'Polonia 1': logobase + '3fzUyCBgUoJ6zqc92KSXh9g9utJEuO.png', - u'Power Turk HD': logobase + '6xL4Vdrzqi4TIuFeum4nnpju6tRguQ.png', u'Pro Все': logobase + 'OavKhOpBMn27qPDKJXXm5rgATgovgn.png', + u'Publika TV': logobase + 'hPRLrpD7ukXhAbmEYnLIJu3naFWWrX.png', u'QTV': logobase + 'zmh1xStjBZGJ6U5g3xLoemD2oT3w3h.png', - u'R1': logobase + 'zI5CoA2qyiJYuXzqsXo0LBmJv9Tern.png', - u'Rai 1': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Rai 2': logobase + '9m3iFYefQC0919T6EfaIhIjiINBuEk.png', - u'Rai News 24': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Rai 1': logobase + 'jTchRtiY0FJ91k1YQxBVHgi0rJ2fJX.png', + u'Rai 2': logobase + 'NwtqcjZNlE2S71D47e4VQua263z0yw.png', + u'Rai 3': logobase + 'IpAZDnIoXaiRiteKWVWNRIVFIlf8Sy.png', + u'Rai News 24': logobase + 'CXDYq1FWPBJgCMUXcOmmfSA4hff9qe.png', + u'RaiNews 24': logobase + 'lSQGWWO5EgBMQYSzctZ6551jifnl8z.png', u'Real Madrid TV': logobase + 'KfoLG7goywcRhMMCc3sO8IVwhuATLp.png', u'Redlight HD': logobase + '5tFZcJtKAZRbXtKGDuLe8FZ3lK9LI5.png', - u'Renome (Одесса)': logobase + 'uXenrBCtSwAsv31RtOAXdn3hXm3i5N.png', + u'Reshet (Israel)': logobase + 'Phkr2nC2VEYdqNjV4wDZ1cKRvm0IfM.png', u'Retro Music TV': logobase + 'zVS9G1oine56udlAK30gNut16iJ9Ft.png', u'Revelation TV': logobase + 'gK8K5dIwNW8YqrGZCilO42UX0NF03i.png', u'RMC TV': logobase + '3CiAgachWg7ohgoU1Gilcm73hXhT41.png', u'RTG HD': logobase + 'g4MDyw0yqXWkIar8eH0cgCz4xhiKON.png', u'RTG TV': logobase + 'IeaOjwR6Q9eJjGZr0LYk2tpchM3ITZ.png', - u'RTVi': logobase + 'QBnba0xrWtpPWLL4yKDRixaCRQAmaP.png', + u'RTL': logobase + 'Ho3hZD4j3ZPbSrbPPw55sUmty44Lqy.png', u'RU TV': logobase + '161.png', + u'RU TV HD': logobase + 'YiMEY6LT8ePoGhXJ7lKz7Os4hY3DOP.png', u'Rusong TV': logobase + '186WxZMn3PGQyMlWsItM9JkSS4Tt29.png', u'Russia Today': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', u'Russia Today Arabic': logobase + 'OPbYfpQc4ShP8JmgPeiavUroY98H8L.png', - u'Russia Today Doc': logobase + 'VOYx5PIhDPrWiZIcCYpm1xrDSQZnsN.png', u'Russia Today Doc HD': logobase + 'b8QePfFi6zsCDS7hfTeFWES5UN4SAk.png', u'Russia Today Doc.': logobase + 'VOYx5PIhDPrWiZIcCYpm1xrDSQZnsN.png', u'Russia Today Espanol': logobase + 'zkPwjfFbktNO8EYDaHlYhwAeT88dmY.png', u'Russia Today HD': logobase + 'rL14fwCe8q10mKTchOwLkfwQVki9XK.png', u'Russian Music Box HD': logobase + 'CbPggS9SpKMrgFW5myDvhLEFvm0hwp.png', - u'Rustavi2': logobase + 'WAgaH51sY7ujcWlPQGWeUCP12zIU8t.png', - u'Rytas TV': logobase + 'TBousteH8Slv9wtLiQ0ssNHXslA4yl.png', + u'SAT.1': logobase + 'eptvXkOPV7lRukQOEmjSvO0yv5aU29.png', u'Satisfaction HD': logobase + 'isdNgbfGENuaDPSMzsz8WMjBzc1rah.png', u'SCT': logobase + 'bDRN0guXHaNHruxtyFkpKYz8J09Xj3.png', u'SET': logobase + 'tKzOKIcOQYrFl1VL8B0QqERFXCAYfU.png', u'SET HD': logobase + 'sX1unYoKj8JR7m8lbtkmPCClRrjAZ9.png', - u'Setanta Sports + HD': logobase + 'jW7pJhmebW2fZsXUTvDuRQLahgiLlU.png', - u'Setanta Sports HD': logobase + 'MZ8syJ7LqKWUIqPJKiwkUvxHNJznW1.png', - u'Setanta Sports Eurasia HD': logobase + '1wgHdJP76TCItF14FxDwBtak8tmxRv.png', u'Sextosenso': logobase + 'HXMvFMLO9weHUcolVmmMKpz98T0K4K.png', - u'Shant TV': logobase + 'IgJpH1JzyjI55Ki2G2Ybz6jzOkwG0T.png', + u'SGDF 24': logobase + 'zgX83hMJYukAhttCXvDa12o959Mt82.png', + u'Shop and Show': logobase + '7NlIUTEOE8fvLFVxXCRqpYzhuuSMmB.png', u'Shop24': logobase + 'bCuxLyvoTk8l5cBRSyKFXjWjYucvlu.png', u'Shopping Live': logobase + 'HVwxC489SYFr8Ttqs1he9RZjEmJjPn.png', u'Shopping TV': logobase + 'Yhipu86vXMGf0hxCTXLezpGpcUbibW.png', - u'SHOPping-TV (Ukraine)': logobase + 'Yhipu86vXMGf0hxCTXLezpGpcUbibW.png', - u'Sky Sports 1': logobase + 'eFxo8gGgylCVPEv9IG4Pud4LdmfAhF.png', - u'Sky Sports 1 HD': logobase + 'U4ngWpMf0MTbeOpX4Tgxelz0tPCBs8.png', - u'Sky Sports 2 HD': logobase + 'B40l6w5yVEpOpZ31io1VzkubFnygLT.png', - u'Sky Sports 5': logobase + 'PL0v2id4iRQoYNIhS4BVyKSPeL8Hor.png', - u'Sky Sports News HQ': logobase + 'v4uIda6tAEaPiumlAg2kqbxlyAheDj.png', + u'SHOW MAX': logobase + 'BwzG83cJD6do7yT2lUE1QKKWlhLPxh.png', + u'Silence TV HD': logobase + 'DoGC3WJCbY8YaqqDRNKozHipJbcv4w.png', u'Sony Sci-Fi': logobase + 'zLWEgf9BbxBr1TR2Qj2NxVQBuPecEP.png', u'Sony Turbo': logobase + 'CaPjVaQrpyN138TarQ7CYBqBOz0ZF7.png', - u'Sport 1 Cz': logobase + 'kCLlfkFz3Ba3BL9Jc9ZPgUKXh2piyv.png', - u'Sport 2 Cz': logobase + 'YLmEjnczWQGJcZC0SxRcH4ifPcwYlx.png', + u'Space TV': logobase + '5WT7hzZe3qEzKV1Diqwyd37xS671BG.png', + u'Spike': logobase + 'KPNj0b4rZBtEB50yasdM72Th7UqrKm.png', + u'Stingray iConcerts HD': logobase + 'E5ATzWWTj9YEisp6P9Vhm6GaYzZb2N.png', u'STV': logobase + 'DbKEKL5gUOFHiruYRjY2H9gTLOV5mu.png', u'Super Tennis HD': logobase + 'mjQW91VJdjIEhADvOO2s6OiKNeUdUK.png', u'Super TV': logobase + 'iRrgl4xiCvibdxWfiQcVUWuYCYCI9K.png', u'TBN Europe': logobase + 'bZT2NYEtReHVOSFTcW8xuDTVDvcoUn.png', u'Tele 5': logobase + 'BahbW0uBxpVOHayoLrr6uFcaBsiYSc.png', u'Tele Vsesvit': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Telemarket': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'TelePace HD': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Telesur': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Teletravel HD': logobase + '4ZlASq3oDpOjXfhwluOzY74sy9elaE.png', - u'TG 3': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'TGirls TV': logobase + 'FufZ2heFswzvAbRkTQZs8UJBYGsxuG.png', + u'Teve2 HD': logobase + 'tWjvTVsZwROaHw0WT4lajACjJ7IcKN.png', + u'The Word Network': logobase + 'R9UwwsopX0YntvnH7OmrENwZrKhuIn.png', u'TiJi': logobase + 'mD3GW0E7rdPwc4stjk7xrLI2gZn4Hq.png', u'TLC': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', u'TLC HD': logobase + 'gT4olUY9nFJbGRCdwd7hHJp1NJ5eJr.png', u'TMB RU': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Tonis': logobase + 'f4NBZiozbHDSxPGRpphIXhvlBjytkG.png', - u'Tonis': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', - u'Tonis HD': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', + u'TOGGO plus': logobase + 'MgIYkMn6crZV90Bc8As0Ljg8L1p9uy.png', u'Top Shop': logobase + 'uD257Lhw7Ko2YD1reC0nRqW7lpy93D.png', - u'Topsong TV': logobase + 'DsJRpcbI6rgjONbQftC5nHt1XMAXYQ.png', - u'Trace Urban HD': logobase + 'v7sxI2w7167Ku0bd9eitaNW508A7Ot.png', - u'Travel + adventure': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', + u'torrent-tv.ru': logobase + '3LPFwCA0df8lBebrPhStZVoNWLMjvk.png', + u'Trace Urban HD': logobase + 'VL7CokCNOQomUM5v9Wmhv2EcnMHdvY.png', u'Travel + adventure HD': logobase + 'b1HifWKMyefmDDvaDAJTwNNTaD8LF4.png', u'Travel Channel': logobase + 'fhmrYjlpC0YMxFd2RqOolbjlXMr0tI.png', u'Travel Channel HD': logobase + 'zfnAGLCvIu1fx9hfrITAZMoo9HYww4.png', + u'Trick HD': logobase + 'g19Pt8UbGKOyNuHvCRlwe8HngcY9cB.png', u'TRT Turk': logobase + 'BDkqm302OLdoXCwpKFVUe8S43OlWj9.png', u'TSN 1': logobase + 'GJsJeixsOKvFaXz8XKqvX4Uh6sUicu.png', u'TV 1000': logobase + 'WJMEvVafVakrm7BUMy1lzku7VQCx25.png', u'TV 1000 Action East': logobase + 'GblbxkDGXZyW5oWt9W8wuERQAiZ7ZT.png', + u'TV 1000 World Kino': logobase + 'A5HJc3MwXrOfesrvG8iCiWGnFYqFOA.png', u'TV 1000 Русское кино': logobase + 'ch5DX6f8hxDnmyzrjotUoKHNGzcw9P.png', - u'TV Bakhoristan': logobase + 'LoXaN929SQC5r5aQ3JETDXwG6VlPMk.png', + u'TV EVROPA Bulgaria': logobase + '78SVTKvw7RzoIMaDyfjdH6tqRtGwF3.png', u'TV Mall': logobase + 'hao8n2RWJzxwNaiVHUawwkNVG6X4Zp.png', - u'TV Rus': logobase + 'MgGR3UkXL6TDO1kA9E9xxH7EYvEwu9.png', - u'TV Safina': logobase + 'mJUmNhJbQqcr2NPppAryEJqDPBJGV0.png', - u'TV Sale': logobase + 'hs0YdiUTlpRtb3wTiP4cXboX0H9oTN.png', u'TV XXI (TV21)': logobase + 'TKchoTWZFRMmGDBok08zoEFJ8mJJCe.png', + u'TV-4 (Украина)': logobase + 'aAcZbGFfyqMbiTybIo3eDV2gty3zFy.png', u'TV-5': logobase + 'iPHeoajUJttv5qZl2yfcOYB1qFx6nv.png', - u'TV1000 Comedy': logobase + 'Rg2qtCBJcy6nGCC0D3EjX4xl1Rn2kw.png', + u'TV1000 Action HD': logobase + 'OdG0mEhFWsZehYaUmQRX2IdtPlGr7y.png', u'TV1000 Comedy HD': logobase + 'ygGiR2hkQLySH6khdo8GV9CyMJ8dXi.png', u'TV1000 Megahit HD': logobase + 'lVPY7WCjn1WM6NL6tfLFy8iGA4yk3Z.png', u'TV1000 Premium HD': logobase + 'raoDrpin8VKmi522LZWzSF0fLRO04m.png', u'TV3 Catalunya': logobase + 'JdogLgSiKoXxC7ZlxVLogeOLMQUQyN.png', - u'TV5 Monde Europe': logobase + 'ko7rbRBnyK1iINkLOA2adRvgVOEgUK.png', - u'TVC 21': logobase + 't6oWXfHEIMAjJ9GSEChuI2JHQg7KzL.png', - u'TVM Channel': logobase + 'aCAKhUCXdMfvBlPD4sHnmOe0LNpPB5.png', - u'TVT 1': logobase + 'CKKdhDfmno9O52tMfWptiAQT0IBWV8.png', - u'UA:Крим': logobase + 'iN5vheceBnPmO18uXHGgKwq7q2PHtF.png', - u'UATV': logobase + '83AcB7NfDhEh4tEcJx7d5Lm1l6dzpB.png', - u'USArmenia TV': logobase + 'llpqN7S6EpCPdoYIHjle6gc4cXlk17.png', - u'UA:Перший (Украина)': logobase + 'iN5vheceBnPmO18uXHGgKwq7q2PHtF.png', - u'UBR': logobase + 'F6EzmjkOBVB0gmn1kQX6itv5VvFml5.png', + u'TV5 Monde': logobase + 'ko7rbRBnyK1iINkLOA2adRvgVOEgUK.png', + u'UA:Крим': logobase + '1oa1CrfXZ94gpdjszpFhOmEtO8mV6K.png', + u'UA:Перший': logobase + '7jcbPt9G0pkO42ROJ6p0CbfyugTBXQ.png', + u'USArmenia TV HD': logobase + 'llpqN7S6EpCPdoYIHjle6gc4cXlk17.png', u'VH1': logobase + '58.png', u'VH1 Classic': logobase + 'FhxUFQ2Bsfom4vb8Ce41gFObAbh1Vh.png', u'Viasat Explore': logobase + 'uCqpsdKP0ialUUYxUk2fXshYdYfxzW.png', u'Viasat Fotboll': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', - u'Viasat Fotboll HD': logobase + '0JLqj3qwFoT1Y61scCyUdWioV5U6hx.png', u'Viasat Golf': logobase + 'IGpQl5iTxaDEPyKffdDEpU0EU0SPiO.png', u'Viasat History': logobase + 'MWGbB8wJp5Gm4vbPHl0ktohDDjMKdr.png', - u'Viasat History HD (Европа)': logobase + '4HljxmfSEerSo6WANdtw0Phz2g9XL8.png', - u'Viasat Hockey': logobase + 'CuAbCRGdf3Z1FGFiwErTbHZ3lAMJzr.png', - u'Viasat Motor': logobase + 'RuYtGxEpqJ5DG7WxGCMWNDXosRdh59.png', u'Viasat Nature East': logobase + 'yimDcPvajJcUKQm9bY15cDdp3rJFcp.png', u'Viasat Nature HD (Европа)': logobase + '6iBmDGCV7UArjU0ZkKnOZDcyB1FbYe.png', u'Viasat Nature-History HD': logobase + 'pSP6zxmuO4PU6xa6KRlZ9L8vvVM2Dy.png', u'Viasat Sport': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Viasat Sport Baltic': logobase + 'ZIITckvF1w5u1MlubmhoG45HxPgcZZ.png', u'Viasat Sport HD': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', - u'Viasat Sport Sverige': logobase + 'prAZKkny3W1HGM03lP0EhzcMmTPZdi.png', u'Vintage TV UK': logobase + 'mhFKSRmQIsgPnIUCWFzWbMAXK5dn3i.png', u'Virgin Radio TV': logobase + '6DgkEMl3HtkpKQbzVovPdSYhy9f3ne.png', - u'Vision Norden': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Visit-X HD': logobase + 'PKLqXFmYlYpo1RbkynS7SfL79gH9fg.png', - u'VOX DE': logobase + 'sfwtJrwyJw1vyIG83Ym08tm5pYIH2Q.png', - u'WBC HD': logobase + 'fMdJvc8moH4HGH5OYqsZwskCowQYuE.png', + u'Viva Austria': logobase + 'sUPeS0888UCOztGNrzvtpXumWwg3ao.png', u'World Fashion': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', u'World Fashion HD': logobase + '2YI4sT9YkGezrw9vZPn0uIRhZ7E2BV.png', + u'Xezer TV': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'XITE HD (NL)': logobase + 'N1zheznjGW9sV4i8GChlBD6sJ3009k.png', + u'XITE Music HD (GR)': logobase + 'FeWCTdXHZKnid3qXQNH7afhkBDdA0t.png', u'XSport': logobase + 'SCwOf3movQkrJf7ez4tNLZYlWo1b7t.png', + u'XSport HD': logobase + 'CK1ynOTAF5wAvilMpyzryKKacuSdDl.png', u'ZDF': logobase + '5SH5FeZiITw27CPxscjksZp272u7He.png', + u'Zee TV': logobase + '1HooaeEhMSvpKmWv6nneZxnTmG5r6Q.png', + u'zhuldyz tv': logobase + 'rleCG7XGat4Ga9MTsfa7JwKQUsJGcD.png', u'ZIK (Украина)': logobase + 'dB1PmpYtgeI0C4u7Cv1QoXHlvc4JtT.png', u'Zoom': logobase + 'SyisYhg411o7z9kXci4vfpLq4KBZZ4.png', + u'Абаза ТВ': logobase + 'ljGzZNT1OOt7L0J9gNql2yl39PbooN.png', + u'Абхазское Телевидение': logobase + '3Mk8W64v1CqxF4S14K8pMNXSVFCthM.png', u'Авто 24': logobase + 'GZwKgvkC0vfYquFAn1dKhppNxdDit9.png', u'Авто плюс': logobase + 'WkRxjy6fJEBJ5NZiaGn2j05eqfFfQq.png', - u'Алматы': logobase + 'PFh3dcBeiHjebw4axqaGQuBe7z5JXD.png', - u'АРТ 24 (Одесса)': logobase + 'H7YvrLp7Zwdb0G4rmB99E5nF4TTmsf.png', + u'АЛА-ТОО 24': logobase + 'Qje1YfQEYAlYacMBfK8sGqCcJnLe7g.png', + u'Анекдот ТВ': logobase + 'foqd1f29CWMTVE8bNziAGglAEgtGPd.png', + u'Арктик ТВ': logobase + 'cwkVCO8OkPO5wNPC9bmVtpuF6iol2b.png', u'Архыз24': logobase + 'tuMbRlnkeMiYQ6u9oeiAVfHF0F20RB.png', - u'Асыл Арна': logobase + 'A6i6ABMom6qyxURqSnbC1ls6bZ6wxO.png', - u'Бел Бизнес Канал': logobase + '5zagkrx7iqLQUxx2jVJM58vT68BP85.png', - u'Беларусь 1': logobase + 'S32pk5GPcXbTyyCllIjfpqPPkBAy4Q.png', - u'Башкир ТВ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Беларусь 24': logobase + 'GxA1KJP5YwpWc38BoPEmLwQH6uDeEz.png', - u'Беларусь 4': logobase + 'xTwlFgOsv50ls1Z9ecK0MhC0hCGX2W.png', - u'Беларусь 5': logobase + '2bzJLE9Sbk9BqUBlsVrtqpsz8h0rB6.png', + u'Белгород 24': logobase + 'AePWQp63Dr37yC4Q7IGyYJl1T5X3a7.png', u'Белсат ТВ': logobase + '9VYuUQxx1ss7ieu2upENtlibyamBP0.png', u'Бигуди': logobase + 'JvcMdB5e6KVBpbXT12ulzmDqenheRx.png', u'Бобер': logobase + '2Edln8vEbg7UUSVUo7lIJPR780OWAR.png', u'Бокс ТВ': logobase + 's3qlrVrIjISf7K0G4HHRYw5rmAej4b.png', + u'Бокс ТВ HD': logobase + 'mU9bGk24P0x0ggxiUvkj0LYOBY7QS1.png', u'Брянская губерния': logobase + 'V5M08NU3jRbd2wKvtVLIRbg9J6ObAZ.png', u'БСТ': logobase + '05dZ8fzOmf1lXYue2OvVsQ21eIeX69.png', + u'БСТ (резерв)': logobase + 'e1Na3L3JV5aPefPbsE9U9VVdWMvkIw.png', + u'БУМ ТВ HD': logobase + 'wHMGSLfPCHU2MX1y1ro6ZqQMxe0pcE.png', + u'В гостях у сказки': logobase + 'j5XrmEogwV5wTwBqFC9G1pD3xw0Yyk.png', u'Винтаж ТВ': logobase + '6yngbEKfgy6XtMw28INQCoOdulf2tC.png', u'Вместе РФ': logobase + 'qa50GYekwBWym7KtoJdzrWHWqN8TeU.png', + u'Вместе РФ HD': logobase + 'JxgnwFeOwqX02WHKKdkWJDoAtpWTCT.png', u'Возрождение ТВ': logobase + 'CKWaxJsLQYiJmnkLhtysIn8hb0NK8b.png', - u'Война и Мир': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Вопросы и ответы': logobase + 'xbV8M35FkvpieQ3TUEL8fhwU8MzjmQ.png', + u'Волга ТВ': logobase + '1CZtoSAY9ZjEavNeGFTrPvSvFxjAHH.png', + u'Волгоград 24': logobase + 'iCZ3kRKXWJITuH34sJ3HPRNedTtk4f.png', u'Время': logobase + 'F44yKDJQLsX0llpZ2wupg8V5vHx5fF.png', - u'ВТВ': logobase + 'svsUD6TinXyv3B1q5sZf3fI9ebmpaF.png', + u'ВТВ': logobase + 'zgmRL374BuIBNBhkkoKUiEDeNMVvPv.png', + u'ВТВ-Плюс': logobase + 'tPmijfGtbGhjmM4y3IT0PzuWbs6dz3.png', u'Галичина': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Глазами туриста': logobase + '0tuGdZaluGh8jDJulzD0MOcHOkKMJI.png', - u'Глазами туриста HD': logobase + 'zNR9KYnBTV8erX7UP9L9C7Daco29mC.png', u'Глас': logobase + '6m1xUI7vFyadFMFxXeIZjlATt7rlWw.png', u'Горизонт': logobase + 'f6wqkzO4WZW7D5Z9xIB4VkMRGmgXUU.png', - u'Горизонт ТВ': logobase + 'o7wnxC58C4KfCnX0hh0il0LTRlPfSg.png', u'Горящий камин HD': logobase + 'ujkL8d4MnAfrWSOBme8UxGFq6AJodT.png', - u'Громадське ТБ HD': logobase + 'Ovkd9TiVv3nLcKPwQS2wkJ85KyYCMQ.png', + u'Громадське HD': logobase + 'tHAyXEoFX2NzCPgAAsbymwTsL6eEEj.png', + u'Губерния (Самара)': logobase + 'm6uotRQ8g6fjMLfVchecqsj0cbCITM.png', u'Дача': logobase + 'mX1irJVUyLtrXr9vfDdhknRqiqV9wA.png', + u'Детектив ТВ': logobase + 'iXWgrqBGDDAT4qNehnEFDBBUu8qeez.png', u'Детский': logobase + 'jk8kody2p38CKdj5KGXWMwRLjgFIlG.png', u'Детский мир': logobase + '00Vf3rPABNnbNQ6Rv0dnfcg3JsJelA.png', - u'Днепропетровский Государственный': logobase + 'DdzgOgvldXBS0n7Vhd78R464TeepUK.png', + u'Дисконт ТВ': logobase + 'jZ5txPxhQBePijTpiiWtH68Pp4My8G.png', u'Дождь': logobase + '381.png', - u'Дождь HD': logobase + '381.png', + u'Доктор': logobase + 'FznqTpJ71LS3RLESzMOHhNDxJMwBE8.png', u'Дом кино': logobase + 'jlC78Fy13KWjQUN6l3FtbsRLZDvc0x.png', - u'Дом кино Премиум HD': logobase + 'uPO7sY6QsL94BV6QZGtLaxmPd75DJB.png', + u'Дом кино Премиум HD': logobase + 'Xg5l3gO5SnER0pwHhgsbHVMqSb42ty.png', u'Домашние животные': logobase + 'HiWAmn5RvUKNJnSW2Jhxjs6maoNFV7.png', u'Домашний': logobase + 'qmqrH2E2EX11qitbIvq0CYsxQjsHGm.png', - u'Домашний (+2)': logobase + 'EbGoVedT7WolQo811cBniNuONJG18q.png', - u'Домашний (+7)': logobase + 'n1Qul20d4Ux9bA8uT6iujBL4B93UO1.png', - u'Домашний (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'Домашний +4': logobase + 'atZkgcX9EReSy2VkatZGbdQyBI2viX.png', u'Домашний магазин': logobase + 'yhzajV7vdLogQzMKat0rLJWaDkPpTP.png', u'Дон 24': logobase + 'qB34tCfiy8RoCwBprbt3yBlb9QTb7i.png', u'Донбас': logobase + 'vsj1IA3Z8QVL3AzzuN4EshgT4LUmRr.png', - u'Драйв ТВ': logobase + 'pmmgMKcRbxeYkjVUVr4IAWM0UuZHO4.png', + u'Драйв ТВ': logobase + 'uzYQb9aFsagXVHjM9hX3oeAbrY3k05.png', u'Еврокино': logobase + '34mszCG0j0Vf6kFcMrLPnFEA8UPdu6.png', - u'Евроновости': logobase + 'K6qQrjlS6ykRlhYGyagaK3jqSXmctx.png', - u'ЕГЭ ТВ': logobase + 'lkZvzcsrdp5eJ4359OcNa5DHaHFEtE.png', + u'Евроновости': logobase + '6HRWXfDaZ5zdwbwOsq383OrtV4MprK.png', + u'ЕГЭ ТВ': logobase + 'dwe86aR0KHW2VzEyj863SViH9wtUGL.png', u'Еда HD': logobase + 'ojUD1jhpv7HBOLmubpEBOsANkpYNtk.png', - u'Еда ТВ': logobase + 'TWdAdMXfMSylb2mQ4efFnOAYosymNC.png', u'Еспресо ТВ': logobase + 'lOwm890F5URuR5Ej7IacerzECPIDt4.png', - u'Жетысу': logobase + 'mLPPIv9pW09vUIdGp70OPsZPhMvUlo.png', - u'Етно HD': logobase + '3SMfgieNX8dCez4flcM8TpCCePrgaq.png', u'Живая Планета': logobase + 'xgKSMwqBdEyXnbVgb8LtNXSMiaPcOx.png', u'Живая природа HD': logobase + 'L9qmq6UIprbg4VBQA46tqRjQ22thsp.png', u'Живи': logobase + 'cOluSjslxxs3JZtSVO8c15xh7h8SDU.png', u'Загородная жизнь': logobase + 'cGGo8HRkVhy66UXKXZ4tH5HyUaaxJA.png', + u'Загородный': logobase + 'RX345W0BBqJbR3XdROHMi4dnbwqwlt.png', u'Звезда': logobase + '0HLRrFHt2QIkbJpLc1fy0RVe7hqCEC.png', u'Звезда (+2)': logobase + 'wa6skHc8W2MRz9Mm3qoKS4LpctSvYs.png', - u'Звезда (16:9)': logobase + 'q5gm6R9h3TAJ4oP5BSViBl0AUgARR3.png', - u'Здоровое ТВ': logobase + '6x1zbASjWqScFk0bghS2RX3H2k5XZf.png', + u'Звезда (+4)': logobase + 'OuOzOS8YQYpfvSiALYw8RE79MLLW8a.png', + u'Звезда (16:9) !': logobase + 'gPzH1fd9puL4joFkEcLAo3C4rc9XR7.png', u'Зоо ТВ': logobase + 'RtAhntWPlKQs6CIYAb72piNF9EsN3E.png', u'Зоопарк': logobase + '1Ugpb5T1THFcFpn19Mnua21KxHkjct.png', + u'Известия HD': logobase + 'oDcPw8gfynentKu5CDXKlA0lo86tdK.png', u'Иллюзион+': logobase + '8LToTNvWRBHvb5IKoteKm8EwAGw8mv.png', u'Индиго': logobase + 'dFIp5shmC5DbfWIDVaFh7coAofmLON.png', u'Индийское кино': logobase + 'y8wvb5nc77vn5c4x8kinv85c7mv6n875c84.png', u'Интер': logobase + '3SP67FapzyZqMVZTPiJIcN09KRkTeu.png', - u'Интер+': logobase + 'QEdaDBbqr13CCfwKQAP77UZYPQIPn0.png', - u'Интерра ТВ': logobase + 'Akn9ntxqkSNrX3BTFRRtXPaEYBszjR.png', - u'Искушение': logobase + 'OQF9mLHIZ87QCphtuNTx6mLJNn5Czf.png', + u'Интер +': logobase + 'gwNJRUrxiQUP4quofAUZwjfn470AkS.png', u'История': logobase + 'PNRaeOUFzOPFtrclFBBRTckj6Lvo0u.png', u'К1': logobase + 'mk2mYb28HFIxkFIiMNQWmKUdn1Y8hD.png', u'К2': logobase + 'IjG76jf8k8HTNLooNpUiEXtkPfA2rG.png', - u'Казахстан': logobase + 'NKuiJTbL08ogaXlIVZkqAYqBUOfFpC.png', u'Калейдоскоп ТВ': logobase + 'YJMqmzlZ87QeXYm9XpjH0XzpjljNcU.png', + u'Камеры Санкт-Петербурга': logobase + 'jHbY0LrB7JKBBWzWC1ctPD6uDqQNnx.png', u'Карусель': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', u'Карусель (+3)': logobase + 'cYXQq7FnkEBAyYxfoPPsL2y3fwMDKU.png', - u'Карусель (+3)': logobase + 'lQ7gn9ZjH9TS7X5JZnEZpJf4Jy5D2D.png', - u'Карусель (+7)': logobase + 'TxPMnikZ6AeVKgUH1TK3OADh9nalHw.png', - u'Карусель International': logobase + 'S233D4b6eq7KOXfdyi4dY2GokKeltg.png', - u'КВН ТВ': logobase + 'xb1VGuwpJFlQ9fWExWngUeSUSC2xJW.png', + u'КВН ТВ': logobase + 'uHs398usENAAKycx5NFCHiC0jDtzrc.png', u'Киевская Русь': logobase + 'C1AZimW2NnNA17H1uJLxxePUMTPQZ7.png', - u'Кино HD': logobase + '5mvhuskUDmyIjUM9WDzZ5u24SeDqmw.png', - u'Кино HD (резерв)': logobase + 'tJiazXmwL8E8jxdZZDCTCyCuD6iit6.png', - u'Кино 2': logobase + '4liS5ZApeFUGXcGL6gFrbw7swx2ZaB.png', u'Кино ТВ': logobase + 'KkITMDICqC1erWdSqyOqoccqde2wHC.png', - u'Кино ТВ HD': logobase + 'lbFIxXL9QKDoAFjooAew0Hoq3mmNzu.png', - u'Кинозал (Боевики)': logobase + 'w9lyoCqdUiL9m5aD6VWg9M6iZ188fd.png', - u'Кинозал (Детский)': logobase + 'wFeLlGKHXw5b0hOT3sTFLtxE9qs9Om.png', - u'Кинозал (Комедия)': logobase + 'cos0nJtFYR3FIqZrkNj7ZyJTj5uo6M.png', - u'Кинозал (Фантастика)': logobase + 'oEMp3C5ys20s6vmtvkJ1YQNf2GwqYn.png', + u'Кино ТВ HD': logobase + 'NGQLEmlltpslVNKkD0htUF7CogGXqX.png', u'Кинокомедия': logobase + 'y087hci8ityixcnyxinxoafhiu.png', u'Киномикс': logobase + 'y6b876ih8g7R876hfug3897wrhj.png', - u'Кинопоказ': logobase + 'v0JEbxExcFI8dVEzCkpZUoktgiS9t7.png', + u'Кинопоказ': logobase + 'L2qsABeMEDKOSG4Tvh7XpfvX2BfksG.png', u'Кинопоказ 1 HD': logobase + 'pNA1vR3sPoovYNKzO4TU6NQcSXNqjk.png', u'Кинопоказ 2 HD': logobase + 'Yf2XKrtorOF2wSZ23Q9NHhL2MfOcqi.png', u'КиноПремиум HD': logobase + 'p580CRZ8bBS6dw3plMWhhxXSzQ59uS.png', @@ -569,16 +504,15 @@ u'Конный мир HD': logobase + 'q87VlmfaBeBhq8SOhAu0SVPEGjiqWM.png', u'Кот ТВ': logobase + 'Rtdtc632nbBQ2TsymgXhkOt7nhytjW.png', u'Красная линия': logobase + 'I43S6jd5noclar0LlPJnyY8adonmUV.png', - u'Крик-ТВ': logobase + 'z4Iv85kqJXWHQJ5475DNxOxlo9Ty5v.png', - u'Круг': logobase + 'fbzJYZiRscBiEvaWEFmzvYzEHdaNN7.png', - u'Крым 1 HD': logobase + 'fVpBXMtyVEJ538kDDcWsezJbgcG8zA.png', + u'Крик-ТВ': logobase + 'bYwacGhYkGwDoMsjGpDbltdCwMJSHJ.png', u'Крым 24': logobase + 'x4s9QQXR8KerO13nlkuEujzcv5ww5F.png', u'КТК': logobase + 'BhI4KURCZ3fAgCQcwQWn7vRCJQblH3.png', u'Кто есть кто': logobase + 'MwNkO3fXd6KefRdiGlOdOQ5q0Zu7kS.png', - u'Кубань 24': logobase + 'CAAqiN96tQzFDdtz3vjrrgeIjAKqNq.png', + u'КТРК Музыка': logobase + 'jqiucXUHkE8UkC4QqeQMt8jcldpJkh.png', + u'КТРК Спорт': logobase + 'x81GWJrYa6mbtQj36q0pg9QnhCKxR1.png', u'Кубань 24 Орбита': logobase + 'FauvJxsKmI5a1fR62uSH9hJfHs5TCr.png', + u'Кузбасс 24': logobase + 'E6ftIMytwxMxG1pbehfWfXdqn0iofP.png', u'Культура (Украина)': logobase + 'pyKdve4YhoChQFGSha8J0FBWBf302a.png', - u'Купи дом ТВ HD': logobase + '7CyfedwVltdaMcZXD3Xx7wa50mZJom.png', u'Курай ТВ': logobase + 'A0onEnFhSzqGxckeyyXsf0ZQ4R3Vc6.png', u'Кухня ТВ': logobase + 'G0WbVMphlP9oJ6KvHRfx0xDfhrF9Re.png', u'КХЛ HD': logobase + 'kRN7BwVtcdaXrU4Mdg24qhFAxjx9oZ.png', @@ -588,6 +522,8 @@ u'Ля-минор': logobase + '8FJA3xMMHcrZuGifHViyVQLjVIem5u.png', u'М1': logobase + 'ezvu2ugYMGnZ968LlnjPw7VjqWIPeM.png', u'М2': logobase + 'U4s78hznNz7mFYZQICkxN7J0HTtlCP.png', + u'Майдан': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Макс 24 (Сочи)': logobase + 'EkfRx44dYCkUuU7g0bM5jJz3bAASBG.png', u'Малыш': logobase + 'lawBkwFuj6qiu3jZQGLnHJUc9wGPwM.png', u'Малятко ТВ': logobase + 'kjYF9vS2IDTMehpzC7WWfjnZ4NVpuk.png', u'Мама': logobase + 'nw9fROQIjjKSDp8Wjkjl1Wt0n0xHxd.png', @@ -614,101 +550,89 @@ u'Матч! Футбол 3 HD': logobase + 'OLHdmyfUev4mMX0OGniJrlUwHnMKOg.png', u'Мега': logobase + 'IXY7dRFoq0qCqn4UbY47iP36vVZ6ck.png', u'Миллет': logobase + 'zDsxAdZp80tZ43Rd2IWAgFOFFYDkwh.png', + u'Министерство идей': logobase + 'k9RMWD6a1nooGeVYJd5ULWhAx7nsTP.png', u'Мир': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', u'Мир (+3)': logobase + 'QxOYkz6f80IdhmC4RSHI1cMd32CqYZ.png', u'Мир 24': logobase + 'auv6717gJOWi0A2VoeDQaCsx9G1NOj.png', u'Мир HD': logobase + 'Oq6h2IicTagHENQu1mFkjLk5rChMnr.png', u'Мир Белогорья': logobase + 'JRU6TyiBAX5Fk84DebhKeSxg7ZkrEf.png', - u'Мир сериала': logobase + 'n3zRAlCCBLl5WOeunWGpuMmfdvSJcW.png', u'Мир сериала': logobase + 'XWDsU7aoUyPPoKfcX7WMYQheuzHJL0.png', u'Мир увлечений': logobase + 'KtELENmDesBwzAAryLgJPqwlr9m17z.png', - u'Многосерийное ТВ': logobase + '4TMYdVpZYXafyIumuB5d7PrjFnslyT.png', + u'Мой мир': logobase + 'cHearBQvhJZUmqc30x3W2yJZHC68MX.png', u'Морской': logobase + 'uzlb3awoyNqvIcf6i35hTVqf7gvuqz.png', u'Москва 24': logobase + 'dZcmoqRoZLhCBh8BE4RnbQivuDY6hH.png', u'Москва Доверие': logobase + '9oPazhJQrGZcSN64ZOS3WjLwGmQIZy.png', u'Моя планета': logobase + 'Qa41eifERrD77xQsmpRGbeTq95Ldlv.png', + u'Моя Удмуртия': logobase + 'l3xs5mws4kXBWbsjeVzS7tp9zttrpp.png', u'Мужское кино': logobase + 'y997iu65e65h4w5d3s4dy.png', - u'Мужское кино HD': logobase + 'xJ3q8xhZTwU3YuZK0CDslvZBNTOUwc.png', u'Мужской': logobase + '6YbhuWNqPKQWWsUGbBnSbAbm7IGssX.png', u'Муз ТВ': logobase + 'gttVvZmkAklbl2i0Mqy1MCzSCn7WiY.png', u'МузСоюз': logobase + 'E4a3fEpdSy2c8AnYEdR8ZIFgFe2LAP.png', u'Музыка Первого': logobase + 'fD2Hnsq5BPMGvobLDMPZP049yNhBYt.png', u'Мульт': logobase + 'ZVzHvGF8mZ6RTsSh6aWsPbF1FBLjyp.png', u'Мультимания': logobase + '132.png', - u'Мультимания HD': logobase + 'xrR3K6dBvelv1nZos2kTbTiU3QnJVK.png', u'Надежда': logobase + 'fvCkzRJPsTx4HONWPv3vPMjQNloPBT.png', u'Нано ТВ': logobase + 'QuURIfJUmXegxsHMYqMivVwxizbfKd.png', - u'Настоящее время HD': logobase + 'Hhq50Ibui6mxfr6tZxKGy9fu2iiBAt.png', + u'Настоящее время HD': logobase + 'Wh8dHDWVAik29wT1fdzdB9QClZ2PWv.png', + u'Наука': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Наука (Украина)': logobase + '73jfedkdp1PVWxdlddOvbwe5HqVEW6.png', u'Наука 2.0': logobase + 'ypWbqYqKApM8cnDK1FibvQgpmgEay9.png', u'Наш детектив HD': logobase + 'ORgdUno4IvNQYiShiEY4srF16JguLs.png', u'Наш кинороман HD': logobase + 'NyV9o2C4xIMFxxYUQsS1qsdAtkqz0k.png', - u'Наш футбол HD': logobase + 'w13cg41Ar3O5MKHWCtYfBWwBBTTial.png', - u'Наше HD': logobase + 'NyV9o2C4xIMFxxYUQsS1qsdAtkqz0k.png', + u'Наша Сибирь': logobase + 'zYJLNsu4daR3jomBUtHPWgeSFQo7Bq.png', + u'Наша Сибирь HD': logobase + 'VbmHqKo1RHl1fkZym386Pc5wTkNM70.png', u'Наше любимое кино': logobase + 'LSR5M6VxB0YDwv6803zrGFkq7vGQ3J.png', u'Наше новое кино': logobase + 'y5jtghJCHG65ukyfjv 45stjxc76.png', - u'НАШЕ НОВОЕ КИНО': logobase + 'y5jtghJCHG65ukyfjv 45stjxc76.png', - u'Наше ТВ': logobase + 'O7Wx8kIB2aXEEmHLwBFIUhxn8B5WQF.png', u'Наше ТВ': logobase + 'gTfxpSWtOGWBq7UHAHcz3XF10bzg78.png', - u'Ника ТВ': logobase + 'pb3d3rBN4qW7ggzsosbAZXflfIv0Ty.png', - u'НЛО ТВ': logobase + '2VGhYruaQo19G1NLGoOiTrwmPxef7d.png', - u'Новое телевидение': logobase + 'n1SkVllOU0IGIx6P2YpIFvNLMmsHfm.png', + u'Неизвестная планета HD': logobase + 'Gvam2aif9yVwK5YjswZ43GQOwc9ZBJ.png', + u'Нова ТВ Болгария': logobase + 'WwgAjjB4yzi7zTG2O5Q0wUUz83O5Eh.png', + u'Новороссия ТВ': logobase + 'YXClYch7fOn1YzpwKjWqjMyPQa2RsV.png', + u'Новосибирские Новости': logobase + 'wN2tOSdrmMjKWUll48Oygdk3ROh9Aj.png', u'Новый канал': logobase + 'k7YdHhVpFZPIkBMXS2P2O2TkZSPf0y.png', + u'Новый мир': logobase + 'K0Oy2d445QmB0921HH1aB5JWUGScto.png', u'Новый Христианский': logobase + 'gf6hTOcGXasvr47vTFRYZGV11xkDr5.png', u'Ностальгия': logobase + 'tIfiXoDaXoZevuGu9pZJSvX8unv1xl.png', u'Ночной клуб': logobase + 'nXifSdkxHJVKI4SKtgtBQmCSHXtgOt.png', u'НСТ': logobase + 'fKYzdlWRz68qd9mRZnWuxMY73EyaSz.png', u'НТВ': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', u'НТВ (+2)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', - u'НТВ (+2)': logobase + '20Ppq9Mmamklh3J27etMM89gFGYPYN.png', - u'НТВ (+3)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', - u'НТВ (+4)': logobase + 'FeYewxBbj2Cz8NOJDl98PTxYXI8cPg.png', - u'НТВ (16:9) ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'НТВ +7': logobase + 'f5N5GvTcqD5JLvdeRPha6LtkgUroht.png', - u'НТВ (+7)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', - u'НТВ (резерв)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', + u'НТВ (+4)': logobase + 'B5GA1cfgmn8EsxrdwfNUIrEbdqarXf.png', u'НТВ HD': logobase + 'zdJ3ye6d3UWl5a56zm6LjqYH6ziSOs.png', - u'НТВ Мир': logobase + 'ykpU49Gl1akVkgiVSsCm5B4TjXvQ64.png', u'НТВ Мир Балтия': logobase + 'ykpU49Gl1akVkgiVSsCm5B4TjXvQ64.png', u'НТВ Право': logobase + '7yNDqWT8KiQ2aa9kGYXSsnVFCPj5xx.png', + u'НТВ Сериал': logobase + 'T7amR78JUYW3EgFZp14hQdh7pjzWLJ.png', + u'НТВ Стиль': logobase + 'DjeH6JTGT2hH0Y1Vfwd9Cceg780nNl.png', u'НТВ+ Инфоканал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'НТВ+ Баскетбол': logobase + 'bIWuyv7DJ65D5hIANkeo9SyIHGUXtn.png', - u'НТВ-ПЛЮС Инфоканал': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'НТК': logobase + 'ix3lD9BcsoKou0MIhUCGgKF9MypV1g.png', u'НТН (Украина)': logobase + 'LpQE1Odb1EoH5dJ90gWjItVyEYBXsw.png', - u'О2 ТВ HD': logobase + 'loCx0MeuHyjR95aFi4EBu0s2xZP50i.png', - u'ОНТ (Одесса)': logobase + 'P3tnfUDZ7f025rw6X7rFNT4aYmwMlJ.png', + u'О!': logobase + 'zHhfMQhsdu59e6puujg0qHt1675jYO.png', + u'О2ТВ': logobase + '7KCLIwxu4Rfly2aMpR8zWBGvpv1Py8.png', + u'О2ТВ HD': logobase + 'KEq35DyCm6eZhUR49IglOsSLvBdmDy.png', u'ОНТ (Беларусь)': logobase + 'a84If8XdqSFa6nHpegdujt52vAGNJW.png', - u'Оплот 2': logobase + 'EqwpuUgrI6Wl6JVDK2fLtXkNaqOXeU.png', - u'Оплот ТВ': logobase + 'gvofGxTug45qSt1vsX0BPzQxGTrwTr.png', + u'Оплот': logobase + 'o6U3Rw8Ts9R3ctV5HTjGYEXxb6AJeS.png', u'Оружие': logobase + 'CyDUCmYXK8WS2kXCX5kiAOFejnlwoP.png', u'Остросюжетное HD': logobase + 'mxF7CZsqsDRMMK4pN8ekdccEgvEsZC.png', - u'ОТВ (Приморск)': logobase + 'W3FNGLfo7R7JpYI98Am5sGamz0bfaN.png', + u'ОТБ (Харьков)': logobase + 'JiLpVicaO0fGahxYY5HqzEGe6rbUdN.png', u'ОТВ (Приморье)': logobase + 'W3FNGLfo7R7JpYI98Am5sGamz0bfaN.png', + u'ОТВ HD (Челябинск)': logobase + 'PJ6O6K8WcAZihwqeWkAyHnBY0aazCq.png', u'Открытый HD': logobase + 'CdIvMNsa7gAihJnpAKX8QnMdpvhAXc.png', - u'Открытый мир': logobase + 'VX8Nc6TW79bbns9IgJI6OdIj0r3DPG.png', + u'Открытый мир': logobase + 'vzFPGE5EMEh8ilV1WIGYcCKwYVVTSZ.png', u'ОТР': logobase + 'CqxKorK72v3ULbWkB3ZOhdte0duYZa.png', u'Охота и рыбалка': logobase + '5l2P20J6ebTh0ptOr27Hh704niP3nU.png', u'Охотник и рыболов': logobase + 'Ws2ddPI0b5Ie7PymoPUsboVlz9lYMS.png', u'Охотник и рыболов HD': logobase + 'O0wexOqiQXKMwr2coDMKWvJEb4zLQ1.png', - u'Парк развлечений': logobase + 'beyfqyeacrFG0PrOeKUQhzQ4bV6Q5d.png', - u'Первый городской Одесса': logobase + 'vBOI3YTA4FDLD0c7BHHjq476p9GMCZ.png', + u'Первый Городской (Киров)': logobase + 'DoKQvt9mUUYEwhXbO4M6PgGOAhok1X.png', u'Первый деловой': logobase + 'a1Qf3MpxC9FPD68Tj8vtUTNK8P25xr.png', u'Первый канал': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', - u'Первый канал (+2)': logobase + 'Lo2zy8h0msIieULsbHOjV1oSInHogr.png', - u'Первый канал (+2)': logobase + '7qA89ulmkQx43LT4XRkDiOepVnUBJ6.png', - u'Первый канал (+4)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', - u'Первый канал (+4)': logobase + 'nHJycH0CkOhPeZ9DmB47iSMWP5HyWz.png', - u'Первый канал (+4) (4:3)': logobase + 'nHJycH0CkOhPeZ9DmB47iSMWP5HyWz.png', - u'Первый канал (+4) HD': logobase + 'NW6N6mMIXx1QVn6SyNYdwumvL1ZWN9.png', - u'Первый канал (+6)': logobase + '45vL1pa1DYHjYyMs1dtGC9RsACLmz3.png', - u'Первый канал (+8)': logobase + 'lRjN5HOOX0txLz8nigpKqpVw5CBKJZ.png', + u'Первый канал (+2)': logobase + 'GGe1ttMtczS0mgzMCegYji57J3PIaJ.png', + u'Первый канал (+2) (16:9)': logobase + 'Vzz9xtVJcWtpfAh4mmz8AreS3FTlae.png', + u'Первый канал (+4)': logobase + 'nUznF8VjPtO3KwbjlU0KaSWhutRRaL.png', + u'Первый канал (+6)': logobase + '0FNeH92R3NfkxmqF5kGJegh7wFS8lP.png', u'Первый канал (4:3)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал (Евразия)': logobase + 'oPB8TcSFAuJtk4hBWgAqC9yNjMpfsi.png', - u'Первый канал (Европа)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', - u'Первый канал (Молдова)': logobase + 'UsbyyrSaJmdjntY4muATwDytnseYYB.png', - u'Первый канал (СНГ)': logobase + 'WimZD6efLd6QotrPP9uiJeF7t50nFv.png', u'Первый канал HD': logobase + 'VxAFWzh1y88c8Aqa17TsxD2IO5pqoi.png', - u'Первый канал HD (резерв)': logobase + 'wT8vUxakbehUwgX5lI9qoRAFBa74tx.png', - u'Первый канал [16:9]': logobase + 'VGBjnqmDOxPOgDXK0seYUkmVloEpr2.png', + u'Первый канал HD (+4)': logobase + 'VxAFWzh1y88c8Aqa17TsxD2IO5pqoi.png', + u'Первый крымский HD': logobase + '83MCfIk9L2xgpTAs9UlPS3Ufsg0EVG.png', u'Первый Метео': logobase + 'vdA7FKd1SCYhX4ovvzjQEHFdW3uA5X.png', u'Первый музыкальный HD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', u'Первый музыкальный UHD': logobase + 'YxKl6Jqi6fmlUJjYGPnBhWhntKzI65.png', @@ -716,71 +640,54 @@ u'Первый музыкальный Россия': logobase + 'MkX2WG1zhZ2KcYFdL0xWH1T4xkO7UW.png', u'Первый музыкальный Россия HD': logobase + 'h7EDhdGypKmtfEP98O052SLlXUCcXt.png', u'Первый образовательный': logobase + '1kXxtStMuodaPU09H3rla3ry3QA2Wr.png', - u'Первый Республиканский': logobase + 'Ubch3Ma8hsdSd8u6spOxVK5bZYsnDs.png', + u'Первый Республиканский телеканал': logobase + 'VyJcFlIDs6xMLIHocDdpb9WeE0JDII.png', u'Первый Ярославский': logobase + '6FZ4QqwM18DLS3WE6XKeF0rJzpSAIH.png', - u'Перец (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', - u'ПИК': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', - u'ПИК HD': logobase + 'P0YqVS65dvcrXyt5BDd0bx02EtK9HC.png', + u'Пёс и Ко': logobase + 'Lj9LwC8sHiSvUM6XDraDuKQPWf1BKK.png', u'Пиксель ТВ': logobase + 'BdCXB7wPZMNvlWzB5xEFzmsYUXcfXW.png', - u'Пингвин Лоло': logobase + 'Z9JfLsdRewyP2JZjxDRs8lVWz6bMhX.png', - u'Планета': logobase + 'o7wnxC58C4KfCnX0hh0il0LTRlPfSg.png', u'Планета HD': logobase + '1QjirpCLi3q9qPu1CTvEvCB0BfINeo.png', u'ПлюсПлюс': logobase + '6gVIy7RMokFO61iVawgwbthe5mhgqm.png', - u'Правда ТУТ': logobase + '6JMI68m3c7uK5DtCvF9HcmCl0R6tJy.png', - u'Про Бизнес': logobase + 'ngUUkphyiFlnVUMIjoHzwrhcwDTsTt.png', + u'Поехали': logobase + '0tjsemk0EPOCa1EKOlwuow2Z9jxXyf.png', + u'Правда тут': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'Про все': logobase + 'S2i01pmEOFJxwJtJCVF7PYc6Ij4jBX.png', - u'Продвижение ТВ': logobase + 'A13CkivHlwonag4N6pzFujtV00lwLL.png', + u'Про жизнь': logobase + 'Robm1kzCcV401H7oo3Fyh18OFOwXRW.png', + u'Продвижение HD (Омск)': logobase + 'qmaGd81WFsVDntpeM8KcNU1mATTV4N.png', u'Просвещение': logobase + 'Fpx3Vqqk2VNcXl4YjsfO53XscWadvF.png', + u'Прямий': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', + u'Прямий HD': logobase + '38BRA5jO6LAsQ6rv1NC3FMJ6KALp8z.png', u'Психология 21': logobase + 'AyLAdiqcKu5X8ykdLf2bO9HsxMlJdO.png', - u'Пушкин - Царское село': logobase + 'Oq0JHpGCMDNzpjf85X7Az3PPPkmDs5.png', u'Пятница': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', - u'ПЯТНИЦА ( +4 )': logobase + 'HjLiq5t608j5kH6yjSiodCoFMUYwQL.png', u'Пятница (+2)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', - u'Пятница (+4)': logobase + 'hCwKaMG6bApehKr68iauaJa68ZC3W1.png', - u'Пятница (+7)': logobase + '3UjE5kGjFxeXtseV0TlUTAHv71aQGO.png', + u'Пятница (+7)': logobase + '0fafj6PSIWdqtBdgwYTl9M06SDU2wA.png', u'Пятый канал': logobase + 'nIUDYY41OO4Xo0ntGpGv2rfpOR5ngt.png', u'Пятый канал (+2)': logobase + 'rsEtkBk2ta1Wj1Y8uvxvSm4Vmbycir.png', - u'Пятый канал (+7)': logobase + 'q0XABDTYA29I5V6AMEqFQDdcr5Bj9k.png', - u'Пятый канал +4': logobase + 'jLZJgPYRXOQWt6vmNpisLyb0HC6wnT.png', + u'Пятый канал (+4)': logobase + 'jLZJgPYRXOQWt6vmNpisLyb0HC6wnT.png', + u'Пятый канал HD': logobase + 'SYEopR9w4oAuBjJKULqiDvAxtB36W9.png', u'Рада (Украина)': logobase + 'hBFJBYNiqZUom0ooVtNEJKliZwfioO.png', u'Радость моя': logobase + 'VRylZFYgFq7AL0FWcbf5JVOX3desn3.png', u'Раз ТВ': logobase + 'CWaPSXdAL4Ejo1wNOqSWNPNVwbbhC7.png', u'РБК': logobase + 'JUMDXZxxB3UiVpMpU8t0aCpbVzxTmP.png', + u'РГВК Дагестан': logobase + 'qn17bOV3KKdSlA4iY9wM3QQGjSYv9f.png', u'РЕН ТВ': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', - u'РЕН ТВ ( +4 )': logobase + 'n0OQ3o7skZJ0B9Htlj01o2VDeXKj0f.png', - u'РЕН ТВ (+4)': logobase + 'lXDeqkRS52EaZoEa7JmoJfzWoGnQ2m.png', - u'РЕН ТВ (резерв)': logobase + 'KHrPZNDBZ3u1RgGGFVULyXl1HveoqO.png', - u'РЕН ТВ (+2)': logobase + 'WeHcyu6MG9GGpCmToRQnoL97iesKk9.png', - u'РЕН ТВ (+7)': logobase + 'bOa17Taj05Dr1k29vFt0hdbOqCeqGQ.png', - u'РЕН ТВ [16:9]': logobase + '8KwidLyxxFby0hoExGeUh4pbaetC16.png', - u'РЕН ТВ HD': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', - u'Ретро ТВ': logobase + 'nf4fvx9VYrz9U4cJZv2xTx09ejd1SE.png', - u'Ретро-ТВ': logobase + 'Of6VAxft82OXf1hk4UonRCHTw3MzqD.png', - u'РЖД ТВ': logobase + 'qa6177IUBWR0hYt1HKXxlMrF5MStOb.png', - u'РЖД ТВ HD': logobase + 'qa6177IUBWR0hYt1HKXxlMrF5MStOb.png', - u'РИА Новости': logobase + 'DixgG6tVZzcVHO2LPQEx3QrtfoVah3.png', - u'Рим ТВ': logobase + '0bwbLdfI8rl5xpxQb3gy1NPxoGh2Ie.png', + u'РЕН ТВ (+2)': logobase + 'LJvkfB2kYaDzij1Y13Fy6syUCkP5Y6.png', + u'РЕН ТВ (+7)': logobase + 'Vs2UxW1Qw5caI5D1xU3J39tVm39SlJ.png', + u'РЕН ТВ (резерв)': logobase + 'KMIosCGWDkcYCFeiSrqOBFxXIDxSHu.png', + u'РЕН-ТВ ИСТОКИ - ОРЕЛ': logobase + 'G9eQwShtkUavBqrQnprfYLZEbTVCxH.png', + u'Ретро ТВ !': logobase + 'IQNV43xGiDarnJoh6G4klfCD3I4kls.png', u'Родное кино': logobase + 'y7saqicb538iqo64ho46hh46.png', u'Россия 1': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', - u'РОССИЯ 1 ( +4 )': logobase + 'jy8HZ6Xf6hroTuXmjqSUgawX7O0g9t.png', - u'Россия 1 (+4)': logobase + 'DL17FIS3R8m6eWTwFvdDYualmxvkGV.png', - u'Россия 1 (+2)': logobase + 'UUrfoqi6NQcc9gRLnCc8ODZJ2T3ShE.png', - u'Россия 1 (+2)': logobase + 'IVnok4EXJmqeIq8Kppprl456zRybNO.png', - u'Россия 1 (+6)': logobase + 'vWy8vur5mokNIzuCKPZpOnj59w8TP8.png', - u'Россия 1 (+8)': logobase + 'WY9uVth2S8kKBB5Jk7OVoXi9Omf02r.png', + u'Россия 1 (+2)': logobase + 'TmknFdEdaX9GxcJhqd856oKgv0o1lp.png', + u'Россия 1 (+4)': logobase + 'buzygzarNA0VXEEKSE2oMALmjSxwao.png', + u'Россия 1 (Мурманск)': logobase + 'iyNAFKplpz55eyI2QzZpOpoI4T6mSL.png', + u'Россия 1 (Чита)': logobase + '508Yuw2bkpv4J4x9LYXOPayVeBbbrq.png', u'Россия 24': logobase + 'LWfGV6eICPYL7psaBfw2dOgGrOtHFS.png', + u'Россия 24 (Мурманск)': logobase + 'CjtIvIg7sTZSlUKXuEVBxchSB8voQp.png', u'Россия HD': logobase + 'ghvqmVpPWqn9x6POAm9UJBvXFzTrqN.png', u'Россия К': logobase + 'W9pWrec1BOJTmj8okrFeyM44wcpyd4.png', - u'Россия К': logobase + 'M0fzdXnwAvdXUdIMtyWGoGmCq2BTTa.png', - u'Россия К (+2)': logobase + 'rVrDeCUhT4RPKIa2h9qRtnDon3Pd9X.png', - u'Россия РТР (Украина)': logobase + '5o9OWeEw90hM5ouECuTLwj5QP8MwU3.png', u'Россия РТР': logobase + '5o9OWeEw90hM5ouECuTLwj5QP8MwU3.png', - u'РТР Планета': logobase + 'wJMTJDEEwIhJWhNL0c6kfs5HVVJOVx.png', - u'Рудана': logobase + '62hGDWG6c90mWQyu1m65r4dpEqe24Y.png', + u'Рудана': logobase + 'sHD2d6haQuqacBMkqiHcrCYJwMed8J.png', u'Русская комедия': logobase + '4Q3vGcJh5o0cgJB18scb90ogvL6OYV.png', u'Русская ночь': logobase + '9Sh9bJuj6js5AJsypAd6UvwnsIB25R.png', u'Русский бестселлер': logobase + 'b5JXaosgmcanh9EVJg52yBefvdLQF7.png', - u'Русский бестселлер Int.': logobase + 'b5JXaosgmcanh9EVJg52yBefvdLQF7.png', u'Русский детектив': logobase + '7I7VjbsFMIkZdoSbHFXiKEVZKNUbOM.png', u'Русский иллюзион': logobase + 'E9Imfr8aHN5midPVpNhJ3fo49FHbQE.png', u'Русский роман': logobase + '2smriIFxtj7Ojh4jyZq0K1XrT98XjS.png', @@ -788,111 +695,100 @@ u'Русский экстрим': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Русский экстрим HD': logobase + 'upndVpIdjY3vb5vrituof5UcKISNcQ.png', u'Рыжий': logobase + 'wfBSy60qHaPSKPpTfrNv9Q167iHIPu.png', - u'Самара ГИС': logobase + 'F0vMjPoFdXa0Vh5t17AOwJVJziMBPG.png', + u'Рыжий (сурд.)': logobase + 'GVloiNkHEd8z66ZjQicsIOkjeInnru.png', u'Санкт-Петербург': logobase + 'sb81YtPOvlHidztMnC5tZPSKkb1uMI.png', + u'Саратов 24': logobase + '5MeGeK5GeEkVWdYKsfFVpcvUcbs39S.png', u'Сарафан ТВ': logobase + 'LsYzwEOUspoxkY2hrTSy9zKqvpWlY8.png', - u'Смайлик ТВ HD': logobase + 'cebz4lM1usjLx0qmFnQTn4Ty1yT0G3.png', - u'Семейное HD': logobase + 'ORgdUno4IvNQYiShiEY4srF16JguLs.png', - u'Сказка HD': logobase + 'dxhsesilX4JjwoCc8Qab4SYe8fxm75.png', - u'Смайл-ТВ': logobase + 'AFH2ud3ZiC5BXXPoq9OVbhCQTS0yOF.png', + u'Светлое ТВ': logobase + 'TI5r40h6Un0bHj8MIVkx6qMJRJTHqa.png', + u'Своё ТВ (Ставрополь)': logobase + 'sZC9lsS9y83orqXpovNOhcP7RdTmzQ.png', + u'Сказки Зайки': logobase + 'ate5hd5vMAzbydl6dVpG6CMyxnqKnE.png', + u'Слуцк ТВ': logobase + 'wty5VfAnYYhOUcThWeSgeaH62b3m5b.png', + u'Смайлик ТВ HD': logobase + 'l4eM2nWbsJxQNKgUP7D4XZ96ieTkb6.png', u'Совершенно секретно': logobase + 'BZJQEpa6Y4KL9tQjPHAIxbodw0KAyN.png', u'Сонце': logobase + 'TJXJVeoBFRMFrUgzpPW4dunJL6XSzn.png', u'Союз': logobase + 'YpsuBorUwulPHW3nI8O6nKETnEVB83.png', u'Спас ТВ': logobase + 'pAFeyS1iCV4BybnpnnwjoKm0y0zvaA.png', - u'Спорт 1 (Украина)': logobase + 'XqwvMS8Hn0mOpbh79esrIqELTsvo5b.png', - u'Спорт 2 (Украина)': logobase + 'q0PokCXx6jtCHEPMvE42I0pD3ZNY0o.png', + u'Спорт 2 (Украина)': logobase + 'caui2OjRMPFWAFtfDnfCG4P1qt7uQj.png', u'СТБ': logobase + 'saZlIDrdaXWoiQa8sfZp2bEAeH0kXk.png', - u'СТВ': logobase + 'W1RY5hkIyvOOr2d8XT6GisDsIFlpbS.png', + u'СТВ (Беларусь)': logobase + 'XX9KxgBXMkvJkP1PeUshGI1pe43Huc.png', u'Страна': logobase + '5G27bahViND43dD1VlkaKlQRsYOqwL.png', u'Страна FM ТВ': logobase + 'ysrRW9deFkccNGlhtT0Sww5Yt8IpY1.png', - u'Страшное HD': logobase + 'Ce9qZfZQAZ8gLs1fb2WCamhsqB6xQN.png', u'СТРК HD': logobase + 'xOmVS1kQFIHeAwqtJbBfrbE75Quj2a.png', u'СТРК HD Сочи': logobase + '2PphESGueDUS1T6wSOr12iTgTbIVJm.png', u'СТС': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', - u'СТС (+1)': logobase + 'WD2ki7yenlw9xaSzIjSEJfEHsD4cmo.png', - u'СТС (+2)': logobase + 'FWNPupxL8N0STWKYEryyiV5sDFN2tS.png', - u'СТС (+2)': logobase + 'CPz3EbrU8SxT3CFj4JXtsx5YBJX3DH.png', u'СТС (+4)': logobase + '9kPWMgG96ZZcBeleMogGOHKZcsOARf.png', u'СТС (+7)': logobase + 'qxxLwx53wKNngJTCtT126zgZT5Wi1x.png', u'СТС (резерв)': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', - u'СТС International': logobase + 'is620Pu6DreVLLnpHkpcXXZC9PI2Hi.png', - u'СТС (рег)': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', u'СТС Love': logobase + 'iciJHbEmJ1hHXAMhzC9cRWhmh9gH0L.png', - u'СТС Love (+7)': logobase + 'HVMQnlAIMnDToeJqFdxKojHUGA0QT1.png', - u'ТА-Одесса': logobase + 'xnCANqGy0KUMtzCttmO9jyZUsXlEEI.png', + u'СТС+ТВ21 (Мурманск)': logobase + 'qXdXqfxDK23uC5srFGAfOlzmaUDYe1.png', u'Тагил ТВ': logobase + 'ubegTi3hkdx7xtZCdOz8K0gBQLlght.png', u'ТБН': logobase + 'r9O7HmwQbFR4oKMH9yKAogE8xBzwz4.png', u'ТВ 3': logobase + '427.png', u'ТВ 3 (+2)': logobase + '427.png', u'ТВ 3 (+3)': logobase + '427.png', u'ТВ 3 (+7)': logobase + 'HfEo6PctsuTnlPQIa0xVKgEDqFwI48.png', - u'ТВ3 ( +4 )': logobase + '7A2g2KEIG6GRcHWjGYCz73jymxAZP1.png', - u'Тверской проспект': logobase + 'k6RPWpDIwfsOZ5qkqHF5ZdOf5GqfmH.png', - u'твоЁтв': logobase + 'sRaEbhHc5usYZI5PWmXlZNPWOFI9qI.png', + u'ТВ FM': logobase + 'A3YxOjZfAw2KJ3naZrCznXfdlotAlw.png', + u'ТВ Центр (+7)': logobase + 'diq0UxVTgqVSQn8KpcYCqe1Tk9q9Wh.png', + u'ТВ Центр (Москва)': logobase + 'F4YXd72KBNgv5iZVXrdPA28uGwISso.png', + u'ТВ Центр Международный': logobase + 'QEpQTskZ9hcfI0rgD8osHVYSv58pde.png', + u'ТВ21+': logobase + 'jir42UhUeeHKMrpUMCfnGdECmP4E5f.png', + u'Твой Дом': logobase + 'TJCGg7LuuUfVsyv5IFjMPLAGZmlNNL.png', + u'Твой Пушкинский': logobase + 'bGue6qSxFjWrP6DjWxeHj8fBBhB161.png', u'ТВЦ': logobase + 'QEpQTskZ9hcfI0rgD8osHVYSv58pde.png', - u'ТВЦ (+2)': logobase + '7zGrgQeXL61DZu4RBOo3KDwnlOjymw.png', - u'ТВЦ (+4)': logobase + 'dR7hMBOIq0MDGMkydFuksHGLNIWz7U.png', - u'ТВЦ (+4)': logobase + 'TJaQ1WxxrKacqXlGR4f7rWwfHAJYol.png', - u'ТВЦ (+7)': logobase + 'dlUvjPT15oxOQ2OD8lYegSkN37mQnJ.png', u'ТДК': logobase + 'eSrHE6Gws4U6JxhFXA3mQ4iDVc0SwS.png', u'Театр': logobase + '8qawrcOtzHZTAa3kI0rcDfVFEZoI5u.png', u'Теледом HD': logobase + 'XviuCfRo0T4WFTOhFaC978AwZ1a3Ge.png', u'Телекафе': logobase + 'fYRFV5oY197jXcyModfWVs0AlrCOIs.png', - u'Телемикс ТВ': logobase + 'FtmRS2h59h41VyWFPh4JtjOGHVjZAB.png', + u'Теленовелла': logobase + 'VmrH6tFL11avii735fJeVryZtEaCQ3.png', u'Телепутешествия': logobase + 'fz4bqwLySJAQkUN7l2EPKNqyvilfRD.png', + u'Терра': logobase + 'GfIwWExYktvecczAx1jL64Rk8xdyea.png', u'ТЕТ': logobase + 'jp0YxRwXOyMWgVfDAyQaXNwle90sV3.png', u'Техно 24': logobase + 'JbUGHLuuZa3WQbjtbzUo0cDZkGnLRK.png', u'Тиса-1': logobase + 'pPRRy0SjPl3JKXdcZrQrdXQz3NTKOH.png', - u'Тлум HD': logobase + 'ZoOHbzwrnWPUaBLOWZJIHPyG5ssYfc.png', + u'Тлум HD': logobase + 'FhxsethirEr5lhrE54VGA4acHu7MJn.png', u'ТНВ-Планета': logobase + 'vBMv8AtIpDhBGQLjPoxkko3baWMFac.png', u'ТНВ-Татарстан': logobase + 'kMdYm3qFLgK52EV0ymvRBB43peSrj9.png', u'ТНТ': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', - u'ТНТ (+1) [Скат]': logobase + 'F5G2d7xGXnFfRMHSDCFiXWAdu1Oe2z.png', u'ТНТ (+2)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', - u'ТНТ (+3)': logobase + 'XIqPuPa3zUzZFevriI0urGganhT8Hm.png', u'ТНТ (+4)': logobase + 'Vtt1KKIpLY4LTQGnV03sdBYyX3hyWR.png', - u'ТНТ (+7)': logobase + '3nipGCZNvlMHrtgLyraU5YEA6sj0ek.png', + u'ТНТ (+7)': logobase + 'lBHVGkAE8EVjDXLQGl52H6HTLavzBR.png', u'ТНТ (резерв)': logobase + 'yEDrU5cgZbdUgfq8kzb40581xLcXNy.png', - u'ТНТ HD': logobase + '8yXyL2mLOMBk3MwA7oeezH4DULY4Ut.png', - u'ТНТ International (Европа)': logobase + 'd4loXdWqOPiwF7thzyBXX8JSspfVjU.png', + u'ТНТ HD': logobase + 'iD2Gi2nqvSSZnrtPeaqAp6M3NpD50v.png', + u'ТНТ International (Беларусь)': logobase + 'd4loXdWqOPiwF7thzyBXX8JSspfVjU.png', u'ТНТ-Music': logobase + '6Go23tY9hpakfrvTEUH7Z7o5Y9hpOG.png', u'ТНТ4': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', - u'ТНТ4 ( +4 )': logobase + '14QPJWtD2gaXxRFVr2v9YxkvwDiT0O.png', u'ТНТ4 (+2)': logobase + 'yTclqOAW0EWwhw9vt0spVSUcS70ZR0.png', - u'ТНТ4 (+3)': logobase + 'dg9BEAtJr4IthpXjxV8NGAiu95NjXl.png', - u'ТНТ4 (+4)': logobase + 'X8ny4KVoh9socLzE8nmCIIEz2xVXBO.png', - u'ТНТ4 (+7)': logobase + 'QMacW5XucDi08h4stQ3AGt69IYAr97.png', u'Томское время': logobase + '1Z7yLtfyuITQhXchO59gwyQ3mES7qS.png', u'Тонус ТВ': logobase + 'bE8WfReOerYTIbqPOo6VD2ajrFdOBT.png', u'Точка ТВ': logobase + 'JWwPbPnkWooIpKd5WYsdpfO3Mh14oA.png', - u'Третий Цифровой Одесса': logobase + 'MXcua7OlJ9CplpD15hD84Xn2QjoCdt.png', - u'Третий цифровой (Одесса)': logobase + 'UERKEoCARXOG4CveFzUNnJoM9eOSwh.png', + u'Третий Цифровой': logobase + 'MXcua7OlJ9CplpD15hD84Xn2QjoCdt.png', + u'Три Ангела': logobase + 'EphQD4X09CGs9ukmPVM5FpmQBSyvch.png', u'ТРК 555': logobase + 'z3GvW4WdjwSmsQrfhAIZewEsdH0rsi.png', u'ТРК Киев': logobase + 'qW0p5z3De7COmSxTmvJ4ZA2wOuSJjg.png', u'ТРК Украина': logobase + '0co3dwhFDhoCVeTbfMV8ASYFYxSrWM.png', + u'ТРК Черновцы': logobase + 'ENrdPt7hypBuec07uRWhxecOTgspkN.png', u'ТРО Союза': logobase + 'xAXy9iMyJ4wa2wmugJvbZuDIzc9pVz.png', u'Трофей': logobase + 'tQTWwjNBC8aLLWiWZfCj43BhWNH51I.png', - u'Трофей HD': logobase + 'cPi9B0icZpuvSRQiH0Kk6rNY8r1X65.png', u'ТТС': logobase + 'crsSIipA6N288sjn4EvUyOyTd0ed9A.png', - u'Туран TV': logobase + 'keRiJb6tKMYm5xhnp53iltppz70qV7.png', - u'Улыбка ребенка': logobase + 'P8aPFN50uJWJHkrqFGb7wgzfaTHUOO.png', + u'Улыбка ребенка HD': logobase + 'P8aPFN50uJWJHkrqFGb7wgzfaTHUOO.png', u'Унiан': logobase + 'fhpFrTDoI9xx7UlK65KAjAbdTGehLL.png', - u'Усадьба': logobase + '5yIxLQzQyZnH5EJcwpSGb28QuRTSFH.png', + u'УрФО 24 HD': logobase + 'dO2b0PDb8ubcYjL4ZxLyZTQ7xjPn0q.png', + u'Усадьба ТВ': logobase + '3PdlNOdFo973qiGewntvIRanZF6MgP.png', u'Успех': logobase + 'RLcfsouYRxTNrQT97AOPIYfSneJyB6.png', - u'УТР': logobase + '83AcB7NfDhEh4tEcJx7d5Lm1l6dzpB.png', + u'Фауна (Украина)': logobase + 'tuGtX9QIXM8h6O6RQoL3S0Gm7a23Zg.png', u'Феникс+ Кино': logobase + 'idiNkkBsxLwxWCF2VZrc9LQEevKh0d.png', u'Футбол': logobase + '472.png', u'Футбол 1 (Украина)': logobase + 'AMKtYwcgSAX5mTcPdhQDe4he18Jz7S.png', - u'Футбол 1 HD': logobase + 'hZaWPKLVxTqUWZk0LTmLi1K1WUzX85.png', u'Футбол 2 (Украина)': logobase + 'PUXTI9mKcs49JnEENkh95KoKqt9VNg.png', - u'Футбол 2 HD': logobase + 'TTvGrBoRM07MHY4q6bSwfzVuDKEGTi.png', + u'Хабар': logobase + 'fb3YNahVC0npAJuKKEEU0x7xkGiuRT.png', u'Хабар 24': logobase + '5ucUtr617J0k3od3cVRn1mMy0Ezi1x.png', - u'Хорошее кино': logobase + 'xEF8TOIGFqtgbOLkMM3TlA20Blel32.png', + u'Хорошее кино': logobase + '9kWkXFAAlhXRtSpF3VJgVD0eMsqRZQ.png', u'Царьград ТВ': logobase + '9DJiua5LxhwBe2Is3l4LDJIH8zf5EE.png', + u'Царьград ТВ HD': logobase + 'WTBbf0ZqL5Ju4AzOLum6NPC5ZGE8Tm.png', u'Центральный канал': logobase + 'vzfLS14qVT0rSphoNeEuO2WDvXFoub.png', u'ЧГТРК Грозный': logobase + 'LqDNdQj6nf4MraZztT6ZnACn7yOJpV.png', u'Че': logobase + 'Hv36ZG48lg1mm2wdxAo3ju1EFS41Ga.png', - u'Че (+2)': logobase + 'N7R86wgoPSELhZ9jCeqaykayjU8mbB.png', + u'Че (+2)': logobase + 'HYWZATW7uW3ajRR7ULEZxcVteqHIqt.png', u'Че (+7)': logobase + 'fV6EjhiQaVyMMlk1era5UxYZOKX7Rf.png', u'Черновицкий Проминь HD': logobase + 'Us5Sr5jEx6SA6eBdZA9xchHzj1hJ4t.png', u'Черноморская телекомпания': logobase + 'LJVoPVcJAE0s4DNmXCga0UPZkRkLTq.png', @@ -900,10 +796,11 @@ u'Шансон ТВ': logobase + 'VY0TyCCkKOj5b8BhBJjT020sQoxL9F.png', u'Эгоист ТВ': logobase + 'moG8uExVh4nw3MN7dmGFdysJHBWLk6.png', u'Эко-ТВ': logobase + 'EmsE2NuqzHi5NXh6OIMMXQpfmD0VIl.png', - u'Эра ТВ': logobase + 'ejWH5qVf8XAWkc3HLQYTmEpYchtaIS.png', + u'Эфир 24 (Татарстан 24)': logobase + '8H6PLKiehQgY46uEPLIzebfSTSnwIx.png', u'Ю': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', u'Ю (+2)': logobase + 'YvnG7hXCwMmHnakp2KkCbqeCigHcuK.png', u'Ювелирочка': logobase + 'pvKHFUCv25R51hJoUpAJfvjGnWyqoZ.png', u'Юмор ТВ': logobase + '6VFA1SVxeFHUsGaKPbNxWZREDkGeZw.png', + u'Юнион': logobase + '0TgIAZbV3nAOO4OxrGGN94atKhnBS2.png', u'Ямал Регион': logobase + 'xapccCaMjlT6JEAkZmk27wzCXlEU2m.png' } \ No newline at end of file diff --git a/plugins/stat_plugin.py b/plugins/stat_plugin.py index 74e7c27..76e59ea 100755 --- a/plugins/stat_plugin.py +++ b/plugins/stat_plugin.py @@ -1,72 +1,83 @@ -''' -Simple statistics plugin +''' +Simple statistics plugin + +To use it, go to http://127.0.0.1:8000/stat +''' +from modules.PluginInterface import AceProxyPlugin +from subprocess import Popen, PIPE +import re +import time +import logging +import urllib2 +import plugins.modules.ipaddr as ipaddr +import locale +import json + +localnetranges = ( + '192.168.0.0/16', + '10.0.0.0/8', + '172.16.0.0/12', + '224.0.0.0/4', + '240.0.0.0/5', + '127.0.0.0/8', + ) + +class Stat(AceProxyPlugin): + handlers = ('stat', 'favicon.icon') + logger = logging.getLogger('STAT') + + def __init__(self, AceConfig, AceStuff): + self.config = AceConfig + self.stuff = AceStuff + + def geo_ip_lookup(self, ip_address): + lookup_url = 'http://freegeoip.net/json/' + ip_address + Stat.logger.debug('Trying to obtain geoip info : ' + lookup_url) + + req = urllib2.Request(lookup_url, headers={'User-Agent' : "Magic Browser"}) + response = json.loads(urllib2.urlopen(req, timeout=10).read()) + + return {'country_code' : '' if not response['country_code'] else response['country_code'] , + 'country' : '' if not response['country_name'] else response['country_name'] , + 'city' : '' if not response['city'] else response['city']} -To use it, go to http://127.0.0.1:8000/stat -''' -from modules.PluginInterface import AceProxyPlugin -import time -import logging -import urllib2 -import plugins.modules.ipaddr as ipaddr -import json - -localnetranges = ( - '192.168.0.0/16', - '10.0.0.0/8', - '172.16.0.0/12', - '224.0.0.0/4', - '240.0.0.0/5', - '127.0.0.0/8', - ) - -class Stat(AceProxyPlugin): - handlers = ('stat', 'favicon.ico') - logger = logging.getLogger('STAT') - - def __init__(self, AceConfig, AceStuff): - self.config = AceConfig - self.stuff = AceStuff - - def geo_ip_lookup(self, ip_address): - lookup_url = 'http://freegeoip.net/json/' + ip_address - Stat.logger.debug('Trying to obtain geoip info : ' + lookup_url) - - req = urllib2.Request(lookup_url, headers={'User-Agent' : "Magic Browser"}) - response = json.loads(urllib2.urlopen(req, timeout=10).read()) - - return {'country_code' : '' if not response['country_code'] else response['country_code'] , - 'country' : '' if not response['country_name'] else response['country_name'] , - 'city' : '' if not response['city'] else response['city']} - - def handle(self, connection, headers_only=False): - current_time = time.time() - - if connection.reqtype == 'favicon.ico': - connection.send_response(404) - return - - connection.send_response(200) - connection.send_header('Content-type', 'text/html; charset=utf-8') - connection.end_headers() - - if headers_only: + def mac_lookup(self,ip_address): + Popen(["ping", "-c 1", ip_address], stdout = PIPE, shell=False) + pid = Popen(["arp", "-n", ip_address], stdout = PIPE, shell=False) + s = pid.communicate()[0] + mac_address = re.search(r"(([a-f\d]{1,2}\:){5}[a-f\d]{1,2})", s) + if mac_address != None: + mac_address = mac_address.groups()[0] + lookup_url = "http://api.macvendors.com/" + mac_address + Stat.logger.debug('Trying to obtain MAC address : ' + lookup_url) + req = urllib2.Request(lookup_url, headers={'User-Agent' : "Magic Browser"}) + response = urllib2.urlopen(req, timeout=10).read() + else: + Stat.logger.debug("Can't obtain MAC address for Local IP") + response = '' + return "Local IP address " if not response else response + + def handle(self, connection, headers_only=False): + current_time = time.time() + + if connection.reqtype == 'favicon.ico': + connection.send_response(404) return - connection.wfile.write('') connection.wfile.write('') connection.wfile.write('AceProxy stat info') connection.wfile.write('') - connection.wfile.write('') - connection.wfile.write('') + connection.wfile.write('') + connection.wfile.write('') connection.wfile.write('

Connected clients: ' + str(self.stuff.clientcounter.total) + '

') connection.wfile.write('
Concurrent connections limit: ' + str(self.config.maxconns) + '
') - connection.wfile.write('') + connection.wfile.write('') for i in self.stuff.clientcounter.clients: for c in self.stuff.clientcounter.clients[i]: connection.wfile.write('') + connection.wfile.write('') else: geo_ip_info = self.geo_ip_lookup(c.handler.clientip) - connection.wfile.write('') + connection.wfile.write('') connection.wfile.write('') connection.wfile.write('') - connection.wfile.write('
Channel nameClient IPLocationStart timeDuration
Channel nameClient IPClient/LocationStart timeDuration
') if c.channelIcon: - connection.wfile.write(' ') + connection.wfile.write(' ') if c.channelName: connection.wfile.write(c.channelName.encode('UTF8')) else: @@ -76,10 +87,10 @@ def handle(self, connection, headers_only=False): clientinrange = any(map(lambda i: ipaddr.IPAddress(c.handler.clientip) in ipaddr.IPNetwork(i),localnetranges)) if clientinrange: - connection.wfile.write('' + 'Local IP adress ' + '' + self.mac_lookup(c.handler.clientip).encode('UTF8').strip() + '' + geo_ip_info.get('country').encode('UTF8') + ', ' + geo_ip_info.get('city').encode('UTF8') + ' ' + '' + geo_ip_info.get('country').encode('UTF8') + ', ' + geo_ip_info.get('city').encode('UTF8') + '  ' + time.strftime('%c', time.localtime(c.connectionTime)) + '' + time.strftime("%H:%M:%S", time.gmtime(current_time-c.connectionTime)) + '
') + connection.wfile.write('') diff --git a/plugins/torrentfilms_plugin.py b/plugins/torrentfilms_plugin.py new file mode 100644 index 0000000..70e3eb6 --- /dev/null +++ b/plugins/torrentfilms_plugin.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +''' +Torrent Films Playlist Plugin +http://ip:port/films +(C) Dorik1972 +''' +import os +import logging +import urllib2 +import base64 +import json +from modules.PluginInterface import AceProxyPlugin +import config.torrentfilms as config +from aceconfig import AceConfig + +class Torrentfilms(AceProxyPlugin): + + handlers = ('torrentfilms', 'films',) + + def __init__(self, AceConfig, AceStuff): + self.logger = logging.getLogger('plugin_TorrentFilms') + self.filelist = None + pass + + def createFilelist(self): + try: + self.logger.debug("Trying to load torrent files from "+config.directory) + if os.path.exists(config.directory): + self.filelist = filter(lambda x: x.endswith(('.torrent','.torrent.added')), os.listdir(config.directory)) + except: + self.logger.error("Can't load torrent files from "+config.directory) + return False + return True + + def getCid(self, filename): + cid = '' + try: + self.logger.debug('Get file name : '+filename) + with open(filename, "rb") as torrent_file: + f = base64.b64encode(torrent_file.read()) + req = urllib2.Request('http://api.torrentstream.net/upload/raw', f) + req.add_header('User-Agent', 'Python-urllib/2.7') + req.add_header('Content-Type', 'application/octet-stream') + cid = json.loads(urllib2.urlopen(req, timeout=10).read())['content_id'] + self.logger.debug("CID: " + cid) + except: + pass + + if cid == '': + logging.debug("Failed to get ContentID from WEB API") + + return None if not cid or cid == '' else cid + + + def handle(self, connection, headers_only=False): + + if not self.filelist: + if not self.createFilelist(): + connection.dieWithError() + return + + hostport = connection.headers['Host'] + connection.send_response(200) + connection.send_header('Content-Type', 'application/x-mpegurl') + connection.end_headers() + + if headers_only: + return; + + connection.wfile.write('#EXTM3U deinterlace=1 m3uautoload=1 cache=1000\n') + + for i in range(len(self.filelist)): + self.filenames = config.directory+'/'+self.filelist[i] + content_id = self.getCid(self.filenames) + + if content_id!='': + req = urllib2.Request('http://'+AceConfig.acehost+':6878/server/api?method=get_media_files&content_id='+content_id) + try: + result = json.loads(urllib2.urlopen(req, timeout=10).read())['result'] + for key in result: + connection.wfile.write('#EXTINF:-1 group-title="TorrentFilms",'+ result[key].encode('UTF-8').translate(None, b"%~}{][^$@*,!?&`|><") +'\n') + connection.wfile.write('http://'+hostport.partition(':')[0]+':6878/ace/getstream?id='+content_id+'&_idx='+key+'\n') + except: + self.logger.debug("Can't load info form "+self.filelist[i]+" file !!") + pass + + self.filelist = None + self.logger.debug('Playlist created!') diff --git a/plugins/torrenttv_plugin.py b/plugins/torrenttv_plugin.py index 99b42da..13f8a31 100644 --- a/plugins/torrenttv_plugin.py +++ b/plugins/torrenttv_plugin.py @@ -96,6 +96,7 @@ def downloadPlaylist(self): self.logomap = logos self.logger.debug("Logos updated") + self.updatelogos = False except: # p2pproxy plugin seems not configured self.updatelogos = False @@ -109,6 +110,7 @@ def handle(self, connection, headers_only=False): # 30 minutes cache if not self.playlist or (int(time.time()) - self.playlisttime > 30 * 60): + self.updatelogos = p2pconfig.email != 're.place@me' and p2pconfig.password != 'ReplaceMe' if not self.downloadPlaylist(): connection.dieWithError() return diff --git a/vlcclient/vlcclient.py b/vlcclient/vlcclient.py index f49f460..851a7b0 100644 --- a/vlcclient/vlcclient.py +++ b/vlcclient/vlcclient.py @@ -26,7 +26,7 @@ class VlcClient(object): def __init__( self, host='127.0.0.1', port=4212, password='admin', connect_timeout=5, - result_timeout=5, out_port=8081): + result_timeout=10, out_port=8081): # Receive buffer self._recvbuffer = None # Output port