From 24aebeed3017aec555d7a274468de28131dd6a5b Mon Sep 17 00:00:00 2001 From: "Xuan (Sean) Hu" Date: Wed, 19 May 2021 16:41:44 +0800 Subject: [PATCH] Channel/Group filter. (#11) * Simplify udpxy parameter. * Add log level arg. * Add group/channel include/exclude param. * Implement group/channel include/exclude. * Add docs. --- docs/parameters.rst | 24 ++++++++++++++++++ iptvtools/constants/defaults.py | 1 + iptvtools/constants/helps.py | 17 +++++++++++++ iptvtools/iptv_filter.py | 8 +++++- iptvtools/models.py | 43 ++++++++++++++++++++++++++++++--- 5 files changed, 88 insertions(+), 5 deletions(-) diff --git a/docs/parameters.rst b/docs/parameters.rst index fa07cd2..d7f1bcd 100644 --- a/docs/parameters.rst +++ b/docs/parameters.rst @@ -3,6 +3,30 @@ Selected Parameters Here is some further explanation for those not so obvious parameters. +GROUP_EXCLUDE +------------- + +Filter the playlist depends on the group title with a blacklist (regular expression). +Note that, it has higher priority than the whitelist ``GROUP_INCLUDE``. + +GROUP_INCLUDE +------------- + +Filter the playlist depends on the group title with a whitelist (regular expression). +Note that, if set, only groups match the pattern will be included. + +CHANNEL_EXCLUDE +--------------- + +Filter the playlist depends on the channel title by a blacklist (regular expression). +Note that, it has higher priority than the whitelist ``CHANNEL_INCLUDE``. + +CHANNEL_INCLUDE +--------------- + +Filter the playlist depends on the channel title by a whitelist (regular expression). +Note that, if set, only channels match the pattern will be included. + MIN_HEIGHT ---------- diff --git a/iptvtools/constants/defaults.py b/iptvtools/constants/defaults.py index 8715c08..a818853 100644 --- a/iptvtools/constants/defaults.py +++ b/iptvtools/constants/defaults.py @@ -10,6 +10,7 @@ CONFIG = 'config.json' INPUTS = ['https://iptv-org.github.io/iptv/index.m3u'] INTERVAL = 1 +LOG_LEVEL = 'INFO' MIN_HEIGHT = 0 OUTPUT = 'iptvtools.m3u' SORT_KEYS = ['tvg-id', 'height', 'title'] diff --git a/iptvtools/constants/helps.py b/iptvtools/constants/helps.py index a7d8a15..74e769f 100644 --- a/iptvtools/constants/helps.py +++ b/iptvtools/constants/helps.py @@ -10,12 +10,29 @@ CONFIG = ( 'Configuration file to unify title and id.' ) +CHANNEL_EXCLUDE = ( + 'Channels to exclude with regex. ' + 'Note: Blacklist has higher priority than whitelist.' +) +CHANNEL_INCLUDE = ( + 'Channels to include with regex. ' + 'Note: Only channels in the whitelist will be included if set.' +) +GROUP_EXCLUDE = ( + 'Groups to exclude with regex.' + 'Note: Blacklist has higher priority than whitelist.' +) +GROUP_INCLUDE = ( + 'Groups to include with regex.' + 'Note: Only groups in the whitelist will be included if set.' +) INPUTS = ( 'One or more input m3u playlist files/urls.' ) INTERVAL = ( 'Interval in seconds between successive fetching requests.' ) +LOG_LEVEL = 'Log level.' MIN_HEIGHT = ( 'Minimum height/resolution to accept, 0 means no resolution filtering.' ) diff --git a/iptvtools/iptv_filter.py b/iptvtools/iptv_filter.py index 9aff965..49ac114 100644 --- a/iptvtools/iptv_filter.py +++ b/iptvtools/iptv_filter.py @@ -23,6 +23,10 @@ def parse_args(): """Arguments Parsers.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--channel-exclude', help=helps.CHANNEL_EXCLUDE) + parser.add_argument('--channel-include', help=helps.CHANNEL_INCLUDE) + parser.add_argument('--group-exclude', help=helps.GROUP_EXCLUDE) + parser.add_argument('--group-include', help=helps.GROUP_INCLUDE) parser.add_argument('--min-height', default=defaults.MIN_HEIGHT, type=int, help=helps.MIN_HEIGHT) parser.add_argument('-c', '--config', default=defaults.CONFIG, @@ -31,6 +35,8 @@ def parse_args(): help=helps.INPUTS) parser.add_argument('-I', '--interval', default=defaults.INTERVAL, type=int, help=helps.INTERVAL) + parser.add_argument('-L', '--log-level', default=defaults.LOG_LEVEL, + help=helps.LOG_LEVEL) parser.add_argument('-o', '--output', default=defaults.OUTPUT, help=helps.OUTPUT) parser.add_argument('-r', '--replace-group-by-source', action='store_true', @@ -54,7 +60,7 @@ def main(): """Filter m3u playlists.""" args = parse_args() - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=args.log_level.upper()) if args.min_height or args.resolution_on_title: if shutil.which('ffprobe') is None: diff --git a/iptvtools/models.py b/iptvtools/models.py index 9533d41..7389a37 100644 --- a/iptvtools/models.py +++ b/iptvtools/models.py @@ -7,8 +7,10 @@ Author: huxuan Email: i(at)huxuan.org """ +import logging import os.path import random +import re import sys import time @@ -63,10 +65,12 @@ def export(self): def parse(self): """Parse contents.""" - self._parse(self.args.inputs, udpxy=self.args.udpxy) + self._parse(self.args.inputs) + logging.debug(self.data) self._parse(self.args.templates, is_template=True) + logging.debug(self.data) - def _parse(self, sources, udpxy=None, is_template=False): + def _parse(self, sources, is_template=False): """Parse playlist sources.""" for source in sources: lines = parsers.parse_content_to_lines(source) @@ -79,14 +83,44 @@ def _parse(self, sources, udpxy=None, is_template=False): lines = lines[1:] current_item = {} + skip = False for line in lines: line = line.strip() if not line: continue + if skip: + skip = False + continue if line.startswith(tags.INF): current_item = parsers.parse_tag_inf(line) current_item = utils.unify_title_and_id(current_item) current_id = current_item['id'] + + if not skip and current_item.get('params') and \ + current_item['params'].get('group-title'): + group = current_item['params']['group-title'] + if not skip and self.args.group_include: + if re.search(self.args.group_include, group): + logging.debug(f'Group to include: `{group}`.') + else: + skip = True + if not skip and self.args.group_exclude and \ + re.search(self.args.group_exclude, group): + skip = True + logging.debug(f'Group to exclude: `{group}`.') + + if not skip and current_item.get('title'): + title = current_item['title'] + if not skip and self.args.channel_include: + if re.search(self.args.channel_include, title): + logging.debug(f'Channel to include: `{title}`.') + else: + skip = True + if not skip and self.args.channel_exclude and \ + re.search(self.args.channel_exclude, title): + skip = True + logging.debug(f'Channel to exclude: `{title}`.') + else: if is_template: for url in self.id_url.get(current_id, []): @@ -94,8 +128,9 @@ def _parse(self, sources, udpxy=None, is_template=False): self.data[url]['params'].update(current_params) self.data[url]['title'] = current_item['title'] else: - if udpxy: - line = utils.convert_url_with_udpxy(line, udpxy) + if self.args.udpxy: + line = utils.convert_url_with_udpxy( + line, self.args.udpxy) current_item['source'] = source_name self.data[line] = current_item