Skip to content

Commit 8f18aca

Browse files
committed
Let --match-filter reject entries early
Makes redundant: `--match-title`, `--reject-title`, `--min-views`, `--max-views`
1 parent 3ad56b4 commit 8f18aca

5 files changed

Lines changed: 40 additions & 24 deletions

File tree

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,10 @@ While these options are redundant, they are still expected to be used due to the
14391439
-e, --get-title --print title
14401440
-g, --get-url --print urls
14411441
-j, --dump-json --print "%()j"
1442+
--match-title REGEX --match-filter "title ~= (?i)REGEX"
1443+
--reject-title REGEX --match-filter "title !~= (?i)REGEX"
1444+
--min-views COUNT --match-filter "view_count >=? COUNT"
1445+
--max-views COUNT --match-filter "view_count <=? COUNT"
14421446

14431447

14441448
#### Not recommended

‎test/test_utils.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,9 +1285,15 @@ def test_match_str(self):
12851285
self.assertTrue(match_str(r'x="foo \& bar" & x^=foo', {'x': 'foo & bar'}))
12861286

12871287
# Example from docs
1288-
self.assertTrue(
1289-
r'!is_live & like_count>?100 & description~=\'(?i)\bcats \& dogs\b\'',
1290-
{'description': 'Raining Cats & Dogs'})
1288+
self.assertTrue(match_str(
1289+
r"!is_live & like_count>?100 & description~='(?i)\bcats \& dogs\b'",
1290+
{'description': 'Raining Cats & Dogs'}))
1291+
1292+
# Incomplete
1293+
self.assertFalse(match_str('id!=foo', {'id': 'foo'}, True))
1294+
self.assertTrue(match_str('x', {'id': 'foo'}, True))
1295+
self.assertTrue(match_str('!x', {'id': 'foo'}, True))
1296+
self.assertFalse(match_str('x', {'id': 'foo'}, False))
12911297

12921298
def test_parse_dfxp_time_expr(self):
12931299
self.assertEqual(parse_dfxp_time_expr(None), None)

‎yt_dlp/YoutubeDL.py‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,12 +1117,15 @@ def check_filter():
11171117
if age_restricted(info_dict.get('age_limit'), self.params.get('age_limit')):
11181118
return 'Skipping "%s" because it is age restricted' % video_title
11191119

1120-
if not incomplete:
1121-
match_filter = self.params.get('match_filter')
1122-
if match_filter is not None:
1123-
ret = match_filter(info_dict)
1124-
if ret is not None:
1125-
return ret
1120+
match_filter = self.params.get('match_filter')
1121+
if match_filter is not None:
1122+
try:
1123+
ret = match_filter(info_dict, incomplete=incomplete)
1124+
except TypeError:
1125+
# For backward compatibility
1126+
ret = None if incomplete else match_filter(info_dict)
1127+
if ret is not None:
1128+
return ret
11261129
return None
11271130

11281131
if self.in_download_archive(info_dict):
@@ -2873,13 +2876,13 @@ def download(self, url_list):
28732876
except UnavailableVideoError:
28742877
self.report_error('unable to download video')
28752878
except MaxDownloadsReached:
2876-
self.to_screen('[info] Maximum number of downloaded files reached')
2879+
self.to_screen('[info] Maximum number of downloads reached')
28772880
raise
28782881
except ExistingVideoReached:
2879-
self.to_screen('[info] Encountered a file that is already in the archive, stopping due to --break-on-existing')
2882+
self.to_screen('[info] Encountered a video that is already in the archive, stopping due to --break-on-existing')
28802883
raise
28812884
except RejectedVideoReached:
2882-
self.to_screen('[info] Encountered a file that did not match filter, stopping due to --break-on-reject')
2885+
self.to_screen('[info] Encountered a video that did not match filter, stopping due to --break-on-reject')
28832886
raise
28842887
else:
28852888
if self.params.get('dump_single_json', False):

‎yt_dlp/options.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,11 @@ def _dict_from_options_callback(
356356
selection.add_option(
357357
'--match-title',
358358
dest='matchtitle', metavar='REGEX',
359-
help='Download only matching titles (regex or caseless sub-string)')
359+
help=optparse.SUPPRESS_HELP)
360360
selection.add_option(
361361
'--reject-title',
362362
dest='rejecttitle', metavar='REGEX',
363-
help='Skip download for matching titles (regex or caseless sub-string)')
363+
help=optparse.SUPPRESS_HELP)
364364
selection.add_option(
365365
'--max-downloads',
366366
dest='max_downloads', metavar='NUMBER', type=int, default=None,
@@ -395,11 +395,11 @@ def _dict_from_options_callback(
395395
selection.add_option(
396396
'--min-views',
397397
metavar='COUNT', dest='min_views', default=None, type=int,
398-
help='Do not download any videos with less than COUNT views')
398+
help=optparse.SUPPRESS_HELP)
399399
selection.add_option(
400400
'--max-views',
401401
metavar='COUNT', dest='max_views', default=None, type=int,
402-
help='Do not download any videos with more than COUNT views')
402+
help=optparse.SUPPRESS_HELP)
403403
selection.add_option(
404404
'--match-filter',
405405
metavar='FILTER', dest='match_filter', default=None,

‎yt_dlp/utils.py‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4657,7 +4657,7 @@ def filter_using_list(row, filterArray):
46574657
return '\n'.join(format_str % tuple(row) for row in table)
46584658

46594659

4660-
def _match_one(filter_part, dct):
4660+
def _match_one(filter_part, dct, incomplete):
46614661
# TODO: Generalize code with YoutubeDL._build_format_filter
46624662
STRING_OPERATORS = {
46634663
'*=': operator.contains,
@@ -4718,7 +4718,7 @@ def _match_one(filter_part, dct):
47184718
'Invalid integer value %r in filter part %r' % (
47194719
m.group('intval'), filter_part))
47204720
if actual_value is None:
4721-
return m.group('none_inclusive')
4721+
return incomplete or m.group('none_inclusive')
47224722
return op(actual_value, comparison_value)
47234723

47244724
UNARY_OPERATORS = {
@@ -4733,22 +4733,25 @@ def _match_one(filter_part, dct):
47334733
if m:
47344734
op = UNARY_OPERATORS[m.group('op')]
47354735
actual_value = dct.get(m.group('key'))
4736+
if incomplete and actual_value is None:
4737+
return True
47364738
return op(actual_value)
47374739

47384740
raise ValueError('Invalid filter part %r' % filter_part)
47394741

47404742

4741-
def match_str(filter_str, dct):
4742-
""" Filter a dictionary with a simple string syntax. Returns True (=passes filter) or false """
4743-
4743+
def match_str(filter_str, dct, incomplete=False):
4744+
""" Filter a dictionary with a simple string syntax. Returns True (=passes filter) or false
4745+
When incomplete, all conditions passes on missing fields
4746+
"""
47444747
return all(
4745-
_match_one(filter_part.replace(r'\&', '&'), dct)
4748+
_match_one(filter_part.replace(r'\&', '&'), dct, incomplete)
47464749
for filter_part in re.split(r'(?<!\\)&', filter_str))
47474750

47484751

47494752
def match_filter_func(filter_str):
4750-
def _match_func(info_dict):
4751-
if match_str(filter_str, info_dict):
4753+
def _match_func(info_dict, *args, **kwargs):
4754+
if match_str(filter_str, info_dict, *args, **kwargs):
47524755
return None
47534756
else:
47544757
video_title = info_dict.get('title', info_dict.get('id', 'video'))

0 commit comments

Comments
 (0)