From 69872deb9055e1b8ee7882d617b2612d7e19b3a9 Mon Sep 17 00:00:00 2001 From: mk-pmb Date: Sun, 13 Mar 2022 19:30:13 +0100 Subject: [PATCH] Add format selection criteria longside/shortside, and tests for both (#30737) --- .../criteria_longside_shortside.py | 117 ++++++++++++++++++ youtube_dl/YoutubeDL.py | 16 ++- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 test/formatselection/criteria_longside_shortside.py diff --git a/test/formatselection/criteria_longside_shortside.py b/test/formatselection/criteria_longside_shortside.py new file mode 100644 index 000000000..b2c58a3d5 --- /dev/null +++ b/test/formatselection/criteria_longside_shortside.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# coding: utf-8 + +from __future__ import unicode_literals + +# Allow direct execution +if __name__ == '__main__': + import os + import sys + repo_dir = os.path.abspath(os.path.join(__file__, '../' * 3)) + sys.path.insert(0, repo_dir) + +import unittest + +from youtube_dl.extractor import YoutubeIE +from test.test_YoutubeDL import YDL, TEST_URL, _make_result + + +default_common_video_properties = { + 'url': TEST_URL, +} + + +def prepare_formats_info_dict(sizes, common={}): + """Convert sizes (id, width, height) to info_dict.""" + formats = [{ + 'format_id': id, + 'width': w, + 'height': h, + **default_common_video_properties, + **common, + } for (id, w, h) in sizes] + info_dict = _make_result(formats) + return info_dict + + +def pick_format_ids(sizes, criteria): + """Check which size(s) match the criteria. Return their IDs.""" + ydl = YDL({'format': criteria}) + yie = YoutubeIE(ydl) + info_dict = prepare_formats_info_dict(sizes) + yie._sort_formats(info_dict['formats']) + ydl.process_ie_result(info_dict.copy()) + # ^-- :TODO: Do we need this .copy()? + picked_ids = [info['format_id'] for info in ydl.downloaded_info_dicts] + return picked_ids + + +class TestFormatSelection(unittest.TestCase): + """Tests class for longside/shortside format selector.""" + + def test_fmtsel_criteria_longside_shortside(self): + """Find largest video within upper limits.""" + # Feature request: https://github.com/ytdl-org/youtube-dl/issues/30737 + + crit = {'tendency': '', 'short': '', 'long': ''} + + def verify_fmtsel(sizes, want): + crit_all = crit['tendency'] + crit['short'] + crit['long'] + picked_ids = pick_format_ids(sizes, crit_all) + self.assertEqual(','.join(picked_ids), want) + + sizes_h = [ + ('A', 256, 144,), + ('B', 426, 240,), + ('C', 640, 360,), + ('D', 854, 480,), + ] + sizes_v = [(id, h, w) for (id, w, h) in sizes_h] + self.assertEqual(sizes_v, [ + # This list is non-authoritative, merely for readers' reference. + ('A', 144, 256,), + ('B', 240, 426,), + ('C', 360, 640,), + ('D', 480, 854,), + ]) + + # def size_by_id(sizes, id): + # return next((s for s in sizes if s[0] == id)) + + def verify_all_shapes_same(expected_id): + verify_fmtsel(sizes_h, expected_id) + verify_fmtsel(sizes_v, expected_id) + + # First, test with no criteria (still empty from initialization above): + # verify_fmtsel(sizes_h, 'D') + # :TODO: ^-- "requested format not available", why not D? + + crit['tendency'] = 'best' + verify_all_shapes_same('D') + + crit['long'] = '[longside<=720]' + verify_all_shapes_same('C') + + crit['long'] = '[longside<=420]' + verify_all_shapes_same('A') + + def shortside_group_1(long, best): + crit['long'] = long + crit['short'] = '[shortside<=720]' + verify_all_shapes_same(best) + + crit['short'] = '[shortside<=420]' + verify_all_shapes_same('C') + + crit['short'] = '[shortside<=360]' + verify_all_shapes_same('C') + + crit['short'] = '[shortside<360]' + verify_all_shapes_same('B') + + shortside_group_1(long='', best='D') + shortside_group_1(long='[longside<=720]', best='C') + + +if __name__ == '__main__': + unittest.main() diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 6f2aba5ac..d7140b119 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -345,6 +345,7 @@ class YoutubeDL(object): 'duration', 'view_count', 'like_count', 'dislike_count', 'repost_count', 'average_rating', 'comment_count', 'age_limit', 'start_time', 'end_time', + 'longside', 'shortside', 'chapter_number', 'season_number', 'episode_number', 'track_number', 'disc_number', 'release_year', 'playlist_index', @@ -1207,7 +1208,7 @@ class YoutubeDL(object): '!=': operator.ne, } operator_rex = re.compile(r'''(?x)\s* - (?Pwidth|height|tbr|abr|vbr|asr|filesize|filesize_approx|fps) + (?Pwidth|height|shortside|longside|tbr|abr|vbr|asr|filesize|filesize_approx|fps) \s*(?P%s)(?P\s*\?)?\s* (?P[0-9.]+(?:[kKmMgGtTpPeEzZyY]i?[Bb]?)?) $ @@ -1495,6 +1496,8 @@ class YoutubeDL(object): formats_info[1].get('format_id')), 'width': formats_info[0].get('width'), 'height': formats_info[0].get('height'), + 'longside': formats_info[0].get('longside'), + 'shortside': formats_info[0].get('shortside'), 'resolution': formats_info[0].get('resolution'), 'fps': formats_info[0].get('fps'), 'vcodec': formats_info[0].get('vcodec'), @@ -1624,6 +1627,17 @@ class YoutubeDL(object): sanitize_string_field(info_dict, 'id') sanitize_numeric_fields(info_dict) + def add_calculated_video_proprties(fmt): + if type(fmt) is not dict: return + dims = [fmt.get(side) for side in ('width', 'height',)] + dims = [n for n in dims if n is not None] + if len(dims): + fmt['shortside'] = min(iter(dims)) + fmt['longside'] = max(iter(dims)) + + for fmt in [info_dict] + (info_dict.get('formats') or []): + add_calculated_video_proprties(fmt) + if 'playlist' not in info_dict: # It isn't part of a playlist info_dict['playlist'] = None