示例#1
0
def update_channels(_config, _namespace, _query_data):
    db = DBChannels(_config)
    ch_data = db.get_channels(_namespace, None)
    results = 'Status Results<ul>'
    for key, values in _query_data.items():
        key_pair = key.split('-', 2)
        uid = key_pair[0]
        instance = key_pair[1]
        name = key_pair[2]
        value = values[0]
        if name == 'enabled':
            value = int(value)

        db_value = None
        for ch_db in ch_data[uid]:
            if ch_db['instance'] == instance:
                db_value = ch_db[name]
                break
        if value != db_value:
            if value is None:
                lookup_name = translate_main2json(name)
                if lookup_name is not None:
                    value = ch_db['json'][lookup_name]
            results += ''.join([
                '<li>Updated [', uid, '][', instance, '][', name, '] to ',
                str(value), '</li>'
            ])
            ch_db[name] = value
            if name == 'thumbnail':
                thumbnail_size = get_thumbnail_size(value)
                ch_db['thumbnail_size'] = thumbnail_size
            db.update_channel(ch_db)
    results += '</ul><hr>'
    return results
示例#2
0
def reset_channels(_config, _name):
    db_channel = DBChannels(_config)
    db_channel.del_status(_name)
    if _name is None:
        return 'Channels updated and will refresh all data on next request'
    else:
        return 'Channels for plugin {} updated and will refresh all data on next request' \
            .format(_name)
示例#3
0
 def __init__(self, _plugins, _namespace, _instance):
     self.logger = logging.getLogger(__name__)
     self.config = _plugins.config_obj.data
     self.epg_db = DBepg(self.config)
     self.channels_db = DBChannels(self.config)
     self.plugins = _plugins
     self.namespace = _namespace
     self.instance = _instance
示例#4
0
 def __init__(self, _webserver):
     self.logger = logging.getLogger(__name__)
     self.webserver = _webserver
     self.config = _webserver.plugins.config_obj.data
     self.epg_db = DBepg(self.config)
     self.channels_db = DBChannels(self.config)
     self.plugins = _webserver.plugins
     self.namespace = _webserver.query_data['name']
     self.instance = _webserver.query_data['instance']
示例#5
0
 def __init__(self, _locast_instance):
     self.locast_instance = _locast_instance
     self.locast = _locast_instance.locast
     self.instance = _locast_instance.instance
     self.db = DBChannels(self.locast_instance.config_obj.data)
     self.config_section = self.locast_instance.config_section
     if self.locast_instance.location.has_dma_changed:
         self.db.del_channels( 
             self.locast.name, self.instance)
         self.db.del_status( 
             self.locast.name, self.instance)
示例#6
0
def get_channels_xml(_config, _base_url, namespace, instance):
    db = DBChannels(_config)
    ch_data = db.get_channels(namespace, instance)
    return_xml = ''
    for sid, sid_data in ch_data.items():
        return_xml = return_xml + \
            tvh_templates['xmlLineup'].format(
                sid_data['number'],
                escape(sid_data['display_name']),
                _base_url + '/' + sid_data['namespace'] + '/watch/' + sid,
                sid_data['json']['HD'])
    return "<Lineup>" + return_xml + "</Lineup>"
示例#7
0
def get_channels_json(_config, _base_url, namespace, instance):
    db = DBChannels(_config)
    ch_data = db.get_channels(namespace, instance)
    return_json = ''
    for sid, sid_data in ch_data.items():
        return_json = return_json + \
            tvh_templates['jsonLineup'].format(
                sid_data['number'],
                sid_data['display_name'],
                _base_url + '/' + sid_data['namespace'] + '/watch/' + sid,
                sid_data['json']['HD'])
        return_json = return_json + ','
    return "[" + return_json[:-1] + "]"
示例#8
0
def get_channels_m3u(_config, _base_url, _namespace, _instance):

    format_descriptor = '#EXTM3U'
    record_marker = '#EXTINF'

    db = DBChannels(_config)
    ch_data = db.get_channels(_namespace, _instance)
    fakefile = StringIO()
    fakefile.write('%s\n' % format_descriptor)

    sids_processed = []
    for sid, sid_data_list in ch_data.items():
        for sid_data in sid_data_list:
            if sid in sids_processed:
                continue
            sids_processed.append(sid)
            if not sid_data['enabled']:
                continue
            # NOTE tvheadend supports '|' separated names in two attributes
            # either 'group-title' or 'tvh-tags'
            # if a ';' is used in group-title, tvheadend will use the
            # entire string as a tag
            groups = sid_data['namespace']
            if sid_data['json']['groups_other']:
                groups += '|' + '|'.join(sid_data['json']['groups_other'])
            if sid_data['json']['HD']:
                if sid_data['json']['group_hdtv']:
                    groups += '|' + sid_data['json']['group_hdtv']
            elif sid_data['json']['group_sdtv']:
                groups += '|' + sid_data['json']['group_sdtv']

            updated_chnum = utils.wrap_chnum(str(sid_data['display_number']),
                                             sid_data['namespace'],
                                             sid_data['instance'], _config)
            service_name = set_service_name(_config, sid_data)
            fakefile.write(
                '%s\n' %
                (record_marker + ':-1' + ' ' + 'channelID=\'' + sid + '\' ' +
                 'tvg-num=\'' + updated_chnum + '\' ' + 'tvg-chno=\'' +
                 updated_chnum + '\' ' + 'tvg-name=\'' +
                 sid_data['display_name'] + '\' ' + 'tvg-id=\'' + sid + '\' ' +
                 (('tvg-logo=\'' + sid_data['thumbnail'] +
                   '\' ') if sid_data['thumbnail'] else '') +
                 'group-title=\'' + groups + '\',' + service_name))
            fakefile.write(
                '%s\n' %
                (('%s%s/%s/watch/%s' %
                  ('http://', _base_url, sid_data['namespace'], str(sid)))))
    return fakefile.getvalue()
示例#9
0
    def init_class_var(cls, _plugins, _hdhr_queue):
        WebHTTPHandler.logger = logging.getLogger(__name__)
        WebHTTPHandler.config = _plugins.config_obj.data

        if platform.system() in ['Windows']:
            unpickle_it = Pickling(WebHTTPHandler.config)
            _plugins = unpickle_it.from_pickle(_plugins.__class__.__name__)
            PluginHandler.cls_plugins = _plugins.plugins

        WebHTTPHandler.plugins = _plugins
        WebHTTPHandler.hdhr_queue = _hdhr_queue
        if not cls.plugins.config_obj.defn_json:
            cls.plugins.config_obj.defn_json = ConfigDefn(
                _config=_plugins.config_obj.data)
        plugins_db = DBPlugins(_plugins.config_obj.data)
        WebHTTPHandler.namespace_list = plugins_db.get_instances()
        WebHTTPHandler.channels_db = DBChannels(_plugins.config_obj.data)
        tmp_rmg_scans = {}
        for plugin_name in _plugins.plugins.keys():
            if 'player-tuner_count' in _plugins.config_obj.data[
                    plugin_name.lower()]:
                tmp_rmg_scans[plugin_name] = []
                for x in range(
                        int(_plugins.config_obj.data[plugin_name.lower()]
                            ['player-tuner_count'])):
                    tmp_rmg_scans[plugin_name].append('Idle')
        WebHTTPHandler.rmg_station_scans = tmp_rmg_scans
        if WebHTTPHandler.total_instances == 0:
            WebHTTPHandler.total_instances = _plugins.config_obj.data['web'][
                'concurrent_listeners']
示例#10
0
 def select_reset_channel(self):
     db_channel = DBChannels(self.config)
     plugins_channel = db_channel.get_channel_names()
     html_option = ''.join([
         '<td nowrap>Plugin: <select id="name" name="name"</select>',
         '<option value="">ALL</option>'
     ])
     for name in plugins_channel:
         html_option = ''.join([
             html_option,
             '<option value="',
             name['namespace'],
             '">',
             name['namespace'],
             '</option>',
         ])
     return ''.join([html_option, '</select></td></tr>'])
示例#11
0
def get_channels_xml(_config, _base_url, _namespace, _instance):
    db = DBChannels(_config)
    ch_data = db.get_channels(_namespace, _instance)
    return_xml = ''
    sids_processed = []
    for sid, sid_data_list in ch_data.items():
        for sid_data in sid_data_list:
            if sid in sids_processed:
                continue
            sids_processed.append(sid)
            if not sid_data['enabled']:
                continue
            updated_chnum = utils.wrap_chnum(str(sid_data['display_number']),
                                             sid_data['namespace'],
                                             sid_data['instance'], _config)
            return_xml = return_xml + \
                ch_templates['xmlLineup'].format(
                    updated_chnum,
                    escape(sid_data['display_name']),
                    _base_url + '/' + sid_data['namespace'] + '/watch/' + sid,
                    sid_data['json']['HD'])
    return "<Lineup>" + return_xml + "</Lineup>"
示例#12
0
def init_class_var(_plugins, _hdhr_queue):
    TunerHttpHandler.logger = logging.getLogger(__name__)
    TunerHttpHandler.plugins = _plugins
    TunerHttpHandler.config = _plugins.config_obj.data
    TunerHttpHandler.hdhr_queue = _hdhr_queue

    if not _plugins.config_obj.defn_json:
        _plugins.config_obj.defn_json = ConfigDefn(
            _config=_plugins.config_obj.data)

    plugins_db = DBPlugins(_plugins.config_obj.data)
    TunerHttpHandler.namespace_list = plugins_db.get_instances()
    TunerHttpHandler.channels_db = DBChannels(_plugins.config_obj.data)

    tmp_rmg_scans = []
    for x in range(int(_plugins.config_obj.data['main']['tuner_count'])):
        tmp_rmg_scans.append('Idle')
    TunerHttpHandler.rmg_station_scans = tmp_rmg_scans
示例#13
0
class EPG:
    def __init__(self, _plugins, _namespace, _instance):
        self.logger = logging.getLogger(__name__)
        self.config = _plugins.config_obj.data
        self.epg_db = DBepg(self.config)
        self.channels_db = DBChannels(self.config)
        self.plugins = _plugins
        self.namespace = _namespace
        self.instance = _instance

    def get_epg_xml(self):
        self.plugins.refresh_epg(self.namespace, self.instance)
        xml_out = self.gen_header_xml()
        channel_list = self.channels_db.get_channels(self.namespace,
                                                     self.instance)
        self.gen_channel_xml(xml_out, channel_list)
        self.epg_db.init_get_query(self.namespace, self.instance)
        day_data = self.epg_db.get_next_row()
        while day_data:
            self.gen_program_xml(xml_out, day_data)
            day_data = self.epg_db.get_next_row()
        epg_dom = minidom.parseString(
            ElementTree.tostring(xml_out, encoding='UTF-8', method='xml'))
        return epg_dom.toprettyxml()

    def gen_channel_xml(self, _et_root, _channel_list):
        for sid, ch_data in _channel_list.items():
            c_out = self.sub_el(_et_root, 'channel', id=sid)
            self.sub_el(
                c_out,
                'display-name',
                text='%s%s%s %s' %
                (self.config['epg']['epg_prefix'], ch_data['display_number'],
                 self.config['epg']['epg_suffix'], ch_data['display_name']))
            self.sub_el(
                c_out,
                'display-name',
                text='%s%s%s %s' %
                (self.config['epg']['epg_prefix'], ch_data['display_number'],
                 self.config['epg']['epg_suffix'],
                 ch_data['json']['callsign']))
            self.sub_el(c_out, 'display-name', text=ch_data['display_number'])
            self.sub_el(c_out,
                        'display-name',
                        text=ch_data['json']['callsign'])
            self.sub_el(c_out, 'display-name', text=ch_data['display_name'])

            if self.config['locast']['epg_channel_icon']:
                self.sub_el(c_out, 'icon', src=ch_data['thumbnail'])
        return _et_root

    def gen_program_xml(self, _et_root, _prog_list):
        for prog_data in _prog_list:
            prog_out = self.sub_el(_et_root,
                                   'programme',
                                   start=prog_data['start'],
                                   stop=prog_data['stop'],
                                   channel=prog_data['channel'])
            if prog_data['title']:
                self.sub_el(prog_out, 'title', text=prog_data['title'])
            if prog_data['subtitle']:
                self.sub_el(prog_out, 'sub-title', text=prog_data['subtitle'])
            descr_add = ''
            if self.config['epg']['description'] == 'extend':

                if prog_data['formatted_date']:
                    descr_add += '(' + prog_data['formatted_date'] + ') '
                if prog_data['genres']:
                    descr_add += ' / '.join(prog_data['genres']) + ' / '
                if prog_data['se_common']:
                    descr_add += prog_data['se_common']
                descr_add += '\n' + prog_data['desc']
            elif self.config['epg']['description'] == 'brief':
                descr_add = prog_data['short_desc']
            elif self.config['epg']['description'] == 'normal':
                descr_add = prog_data['desc']
            else:
                self.logger.warning(
                    'Config value [epg][description] is invalid: ' +
                    self.config['epg']['description'])
            self.sub_el(prog_out, 'desc', text=descr_add)

            if prog_data['video_quality']:
                video_out = self.sub_el(prog_out, 'video')
                self.sub_el(video_out, 'quality', prog_data['video_quality'])

            if prog_data['air_date']:
                self.sub_el(prog_out,
                            'date',
                            lang='en',
                            text=prog_data['air_date'])

            self.sub_el(prog_out,
                        'length',
                        units='minutes',
                        text=str(prog_data['length']))

            if prog_data['genres']:
                for f in prog_data['genres']:
                    if self.config['epg']['genre'] == 'normal':
                        pass
                    elif self.config['epg']['genre'] == 'tvheadend':
                        if f in epg_category.TVHEADEND.keys():
                            f = epg_category.TVHEADEND[f]
                    else:
                        self.logger.warning(
                            'Config value [epg][genre] is invalid: ' +
                            self.config['epg']['genre'])
                    self.sub_el(prog_out,
                                'category',
                                lang='en',
                                text=f.strip())

            if prog_data['icon'] and self.config['locast']['epg_program_icon']:
                self.sub_el(prog_out, 'icon', src=prog_data['icon'])

            if prog_data['rating']:
                r = ElementTree.SubElement(prog_out, 'rating')
                self.sub_el(r, 'value', text=prog_data['rating'])

            if prog_data['se_common']:
                self.sub_el(prog_out,
                            'episode-num',
                            system='common',
                            text=prog_data['se_common'])
                self.sub_el(prog_out,
                            'episode-num',
                            system='xmltv_ns',
                            text=prog_data['se_xmltv_ns'])
                self.sub_el(prog_out,
                            'episode-num',
                            system='SxxExx',
                            text=prog_data['se_common'])

            if prog_data['is_new']:
                self.sub_el(prog_out, 'new')

    def gen_header_xml(self):
        xml_out = ElementTree.Element('tv')
        xml_out.set('source-info-url', 'https://www.locast.org')
        xml_out.set('source-info-name', 'locast.org')
        xml_out.set('generator-info-name', 'locastepg')
        xml_out.set('generator-info-url',
                    'github.com/rocky4546/tvheadend-locast')
        xml_out.set('generator-special-thanks', 'locast2plex')
        return xml_out

    def sub_el(self, parent, name, text=None, **kwargs):
        el = ElementTree.SubElement(parent, name, **kwargs)
        if text:
            el.text = text
        return el
示例#14
0
class Channels:
    logger = None

    def __init__(self, _locast_instance):
        self.locast_instance = _locast_instance
        self.locast = _locast_instance.locast
        self.instance = _locast_instance.instance
        self.db = DBChannels(self.locast_instance.config_obj.data)
        self.config_section = self.locast_instance.config_section
        if self.locast_instance.location.has_dma_changed:
            self.db.del_channels( 
                self.locast.name, self.instance)
            self.db.del_status( 
                self.locast.name, self.instance)

    def refresh_channels(self, force=False):
        last_update = self.db.get_status(self.locast.name, self.instance)
        update_needed = False
        if not last_update:
            update_needed = True
        else:
            delta = datetime.datetime.now() - last_update
            if delta.days >= self.locast_instance.config_obj.data[self.locast.name.lower()]['channel_update_timeout']:
                update_needed = True
        if update_needed or force:
            ch_dict = self.get_locast_channels()
            self.db.save_channel_list(self.locast.name, self.instance, ch_dict)
        else:
            self.logger.debug('Channel data still new for {} {}, not refreshing'.format(self.locast.name, self.instance))

    @handle_json_except
    @handle_url_except
    def get_locast_channels(self):
        channels_url = 'https://api.locastnet.org/api/watch/epg/{}' \
            .format(self.locast_instance.location.dma)
        url_headers = {
            'Content-Type': 'application/json',
            'authorization': 'Bearer {}'.format(self.locast_instance.token),
            'User-agent': constants.DEFAULT_USER_AGENT}
        req = urllib.request.Request(channels_url, headers=url_headers)
        with urllib.request.urlopen(req) as resp:
            ch_json = json.load(resp)

        ch_list = []
        if len(ch_json) == 0:
            self.logger.warning('Locast HTTP Channel Request Failed for instance {}'.format(self.instance))
            raise exceptions.CabernetException('Locast HTTP Channel Request Failed')

        self.logger.info("{}: Found {} stations for DMA {} on instance {}"
            .format(self.locast.name, len(ch_json),
            str(self.locast_instance.location.dma), self.instance))

        for locast_channel in ch_json:
            hd = 0
            ch_id = str(locast_channel['id'])
            ch_callsign = locast_channel['name']
            thumbnail = None
            thumbnail_size = None
            if 'logoUrl' in locast_channel.keys():
                thumbnail = locast_channel['logoUrl']
            elif 'logo226Url' in locast_channel.keys():
                thumbnail = locast_channel['logo226Url']
            if thumbnail is not None:
                thumbnail_size = channels.get_thumbnail_size(thumbnail)
            try:
                if 'videoProperties' in locast_channel['listings'][0]:
                    if 'HD' in locast_channel['listings'][0]['videoProperties']:
                        hd = 1
                    else:
                        hd = 0
            except IndexError:
                pass

            try:
                assert (float(locast_channel['callSign'].split()[0]))
                channel = locast_channel['callSign'].split()[0]
                friendly_name = locast_channel['callSign'].split()[1]
                channel = {
                    'id': ch_id,
                    'enabled': True,
                    'callsign': ch_callsign,
                    'number': channel,
                    'name': friendly_name,
                    'HD': hd,
                    'group_hdtv': self.locast_instance.config_obj.data[self.config_section]['m3u-group_hdtv'],
                    'group_sdtv': self.locast_instance.config_obj.data[self.config_section]['m3u-group_sdtv'],
                    'groups_other': None,  # array list of groups/categories
                    'thumbnail': thumbnail,
                    'thumbnail_size': thumbnail_size
                }
                ch_list.append(channel)
            except ValueError:
                self.logger.warning(
                    '################### CALLSIGN ERROR Channel ignored: {} {}'
                        .format(ch_id, locast_channel['callSign']))
        return ch_list

    @handle_json_except
    @handle_url_except
    def get_channel_uri(self, _channel_id):
        self.logger.info(self.locast.name + ": Getting station info for " + _channel_id)
        stream_url = ''.join([
            'https://api.locastnet.org/api/watch/station/',
            str(_channel_id), '/',
            self.locast_instance.location.latitude, '/',
            self.locast_instance.location.longitude])
        stream_headers = {'Content-Type': 'application/json',
            'authorization': 'Bearer ' + self.locast_instance.token,
            'User-agent': constants.DEFAULT_USER_AGENT}
        req = urllib.request.Request(stream_url, headers=stream_headers)
        with urllib.request.urlopen(req) as resp:
            stream_result = json.load(resp)
        self.logger.debug("Determining best video stream for " + _channel_id + "...")
        bestStream = None

        # find the heighest stream url resolution and save it to the list
        videoUrlM3u = m3u8.load(stream_result['streamUrl'],
            headers={'authorization': 'Bearer ' + self.locast_instance.token,
                'User-agent': constants.DEFAULT_USER_AGENT})
        self.logger.debug("Found " + str(len(videoUrlM3u.playlists)) + " Playlists")

        if len(videoUrlM3u.playlists) > 0:
            for videoStream in videoUrlM3u.playlists:
                if bestStream is None:
                    bestStream = videoStream

                elif ((videoStream.stream_info.resolution[0] > bestStream.stream_info.resolution[0]) and
                      (videoStream.stream_info.resolution[1] > bestStream.stream_info.resolution[1])):
                    bestStream = videoStream

                elif ((videoStream.stream_info.resolution[0] == bestStream.stream_info.resolution[0]) and
                      (videoStream.stream_info.resolution[1] == bestStream.stream_info.resolution[1]) and
                      (videoStream.stream_info.bandwidth > bestStream.stream_info.bandwidth)):
                    bestStream = videoStream

            if bestStream is not None:
                self.logger.debug(_channel_id + " will use " +
                    str(bestStream.stream_info.resolution[0]) + "x" +
                    str(bestStream.stream_info.resolution[1]) +
                    " resolution at " + str(bestStream.stream_info.bandwidth) + "bps")

                return bestStream.absolute_uri

        else:
            self.logger.debug("No variant streams found for this station.  Assuming single stream only.")
            return stream_result['streamUrl']
示例#15
0
 def __init__(self, _locast):
     self.locast = _locast
     self.db = DBChannels(self.locast.config)
示例#16
0
class EPG:
    # https://github.com/XMLTV/xmltv/blob/master/xmltv.dtd
    def __init__(self, _webserver):
        self.logger = logging.getLogger(__name__)
        self.webserver = _webserver
        self.config = _webserver.plugins.config_obj.data
        self.epg_db = DBepg(self.config)
        self.channels_db = DBChannels(self.config)
        self.plugins = _webserver.plugins
        self.namespace = _webserver.query_data['name']
        self.instance = _webserver.query_data['instance']

    def get_epg_xml(self, _webserver):
        try:
            _webserver.do_dict_response({
                'code': 200,
                'headers': {
                    'Content-type':
                    'application/xml; Transfer-Encoding: chunked'
                },
                'text': None
            })

            self.plugins.refresh_epg(self.namespace, self.instance)
            xml_out = self.gen_header_xml()
            channel_list = self.channels_db.get_channels(
                self.namespace, self.instance)
            self.gen_channel_xml(xml_out, channel_list)
            self.write_xml(xml_out, keep_xml_prolog=True)
            xml_out = None

            self.epg_db.init_get_query(self.namespace, self.instance)
            day_data, ns, inst = self.epg_db.get_next_row()
            self.prog_processed = []
            data_written = False
            while day_data:
                xml_out = self.gen_minimal_header_xml()
                self.gen_program_xml(xml_out, day_data, channel_list, ns, inst)
                did_write_data = self.write_xml(xml_out)
                data_written = did_write_data or data_written
                day_data, ns, inst = self.epg_db.get_next_row()
            self.epg_db.close_query()
            if data_written:
                self.webserver.wfile.write(b'</tv>\r\n')
            else:
                self.webserver.wfile.write(b'<tv/>\r\n')
            self.webserver.wfile.flush()
        except MemoryError as e:
            self.logger.error('MemoryError parsing large xml')
            raise e
        xml_out = None

    def write_xml(self, _xml, keep_xml_prolog=False):
        if self.config['epg']['epg_prettyprint']:
            if not keep_xml_prolog:
                epg_dom = ElementTree.tostring(_xml)
                if len(epg_dom) < 20:
                    return False
                epg_dom = minidom.parseString(epg_dom).toprettyxml()[27:-6]
            else:
                epg_dom = minidom.parseString(
                    ElementTree.tostring(_xml, encoding='UTF-8',
                                         method='xml')).toprettyxml()[:-6]
                if len(epg_dom) < 250:
                    return False
            self.webserver.wfile.write(epg_dom.encode())
        else:
            if not keep_xml_prolog:
                epg_dom = ElementTree.tostring(_xml)[4:-5]
                if len(epg_dom) < 10:
                    return False
            else:
                epg_dom = ElementTree.tostring(_xml)[:-5]
                if len(epg_dom) < 250:
                    return False
            self.webserver.wfile.write(epg_dom + b'\r\n')
        epg_dom = None
        return True

    def gen_channel_xml(self, _et_root, _channel_list):
        sids_processed = []
        for sid, sid_data_list in _channel_list.items():
            if sid in sids_processed:
                continue
            sids_processed.append(sid)
            for ch_data in sid_data_list:
                if not ch_data['enabled']:
                    break
                updated_chnum = utils.wrap_chnum(ch_data['display_number'],
                                                 ch_data['namespace'],
                                                 ch_data['instance'],
                                                 self.config)
                c_out = EPG.sub_el(_et_root, 'channel', id=sid)
                EPG.sub_el(c_out,
                           'display-name',
                           _text='%s %s' %
                           (updated_chnum, ch_data['display_name']))
                EPG.sub_el(c_out,
                           'display-name',
                           _text='%s %s' %
                           (updated_chnum, ch_data['json']['callsign']))
                EPG.sub_el(c_out, 'display-name', _text=updated_chnum)
                EPG.sub_el(c_out,
                           'display-name',
                           _text=ch_data['json']['callsign'])
                EPG.sub_el(c_out,
                           'display-name',
                           _text=ch_data['display_name'])

                if self.config['epg']['epg_channel_icon']:
                    EPG.sub_el(c_out, 'icon', src=ch_data['thumbnail'])
                break
        return _et_root

    def gen_program_xml(self, _et_root, _prog_list, _channel_list, _ns, _inst):
        for prog_data in _prog_list:
            proginfo = prog_data['start'] + prog_data['channel']
            if proginfo in self.prog_processed:
                continue
            skip = False
            try:
                for ch_data in _channel_list[prog_data['channel']]:
                    if ch_data['namespace'] == _ns \
                            and ch_data['instance'] == _inst \
                            and not ch_data['enabled']:
                        skip = True
                        break
            except KeyError:
                skip = True
            if skip:
                continue
            self.prog_processed.append(proginfo)

            prog_out = EPG.sub_el(_et_root,
                                  'programme',
                                  start=prog_data['start'],
                                  stop=prog_data['stop'],
                                  channel=prog_data['channel'])
            if prog_data['title']:
                EPG.sub_el(prog_out,
                           'title',
                           lang='en',
                           _text=prog_data['title'])
            if prog_data['subtitle']:
                EPG.sub_el(prog_out,
                           'sub-title',
                           lang='en',
                           _text=prog_data['subtitle'])
            descr_add = ''
            if self.config['epg']['description'] == 'extend':
                if prog_data['formatted_date']:
                    descr_add += '(' + prog_data['formatted_date'] + ') '
                if prog_data['genres']:
                    descr_add += ' / '.join(prog_data['genres']) + ' / '
                if prog_data['se_common']:
                    descr_add += prog_data['se_common']
                descr_add += '\n' + prog_data['desc']
            elif self.config['epg']['description'] == 'brief':
                descr_add = prog_data['short_desc']
            elif self.config['epg']['description'] == 'normal':
                descr_add = prog_data['desc']
            else:
                self.logger.warning(
                    'Config value [epg][description] is invalid: ' +
                    self.config['epg']['description'])
            EPG.sub_el(prog_out, 'desc', lang='en', _text=descr_add)

            if prog_data['video_quality']:
                video_out = EPG.sub_el(prog_out, 'video')
                EPG.sub_el(video_out, 'quality', prog_data['video_quality'])

            if prog_data['air_date']:
                EPG.sub_el(prog_out, 'date', _text=prog_data['air_date'])

            EPG.sub_el(prog_out,
                       'length',
                       units='minutes',
                       _text=str(prog_data['length']))

            if prog_data['genres']:
                for f in prog_data['genres']:
                    if self.config['epg']['genre'] == 'normal':
                        pass
                    elif self.config['epg']['genre'] == 'tvheadend':
                        if f in epg_category.TVHEADEND.keys():
                            f = epg_category.TVHEADEND[f]
                    else:
                        self.logger.warning(
                            'Config value [epg][genre] is invalid: ' +
                            self.config['epg']['genre'])
                    EPG.sub_el(prog_out,
                               'category',
                               lang='en',
                               _text=f.strip())

            if prog_data['icon'] and self.config['epg']['epg_program_icon']:
                EPG.sub_el(prog_out, 'icon', src=prog_data['icon'])

            if prog_data['actors'] or prog_data['directors']:
                r = ElementTree.SubElement(prog_out, 'credits')
                if prog_data['directors']:
                    for actor in prog_data['directors']:
                        EPG.sub_el(r, 'producer', _text=actor)
                if prog_data['actors']:
                    for actor in prog_data['actors']:
                        EPG.sub_el(r, 'presenter', _text=actor)

            if prog_data['rating']:
                r = ElementTree.SubElement(prog_out, 'rating')
                EPG.sub_el(r, 'value', _text=prog_data['rating'])

            if prog_data['se_common']:
                EPG.sub_el(prog_out,
                           'episode-num',
                           system='common',
                           _text=prog_data['se_common'])
                EPG.sub_el(prog_out,
                           'episode-num',
                           system='dd_progid',
                           _text=prog_data['se_progid'])
                EPG.sub_el(prog_out,
                           'episode-num',
                           system='xmltv_ns',
                           _text=prog_data['se_xmltv_ns'])
                EPG.sub_el(prog_out,
                           'episode-num',
                           system='SxxExx',
                           _text=prog_data['se_common'])

            if prog_data['is_new']:
                EPG.sub_el(prog_out, 'new')
            else:
                EPG.sub_el(prog_out, 'previously-shown')
            if prog_data['cc']:
                EPG.sub_el(prog_out, 'subtitles', type='teletext')
            if prog_data['premiere']:
                EPG.sub_el(prog_out, 'premiere')

    def gen_header_xml(self):
        if self.namespace is None:
            website = utils.CABERNET_URL
            name = utils.CABERNET_NAME
        else:
            website = self.plugins.plugins[
                self.namespace].plugin_settings['website']
            name = self.plugins.plugins[self.namespace].plugin_settings['name']

        xml_out = ElementTree.Element('tv')
        xml_out.set('source-info-url', website)
        xml_out.set('source-info-name', name)
        xml_out.set('generator-info-name', utils.CABERNET_NAME)
        xml_out.set('generator-info-url', utils.CABERNET_URL)
        xml_out.set('generator-special-thanks', 'locast2plex')
        return xml_out

    def gen_minimal_header_xml(self):
        return ElementTree.Element('tv')

    @staticmethod
    def sub_el(_parent, _name, _text=None, **kwargs):
        el = ElementTree.SubElement(_parent, _name, **kwargs)
        if _text:
            el.text = _text
        return el