diff --git a/test/formatselection/criteria_longside_shortside.py b/test/formatselection/criteria_longside_shortside.py new file mode 100644 index 000000000..3979f45c6 --- /dev/null +++ b/test/formatselection/criteria_longside_shortside.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# coding: utf-8 +"""Tests module for longside/shortside format selector.""" + +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.""" + def make_one_format(size): + d = default_common_video_properties.copy() + (d['format_id'], d['width'], d['height']) = size + d.update(common) + return d + + info_dict = _make_result([make_one_format(size) for size in sizes]) + 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()) + 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, 'A') + + 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/test/test_YoutubeDL.py b/test/test_YoutubeDL.py index d994682b2..7040cd93a 100644 --- a/test/test_YoutubeDL.py +++ b/test/test_YoutubeDL.py @@ -136,6 +136,11 @@ class TestFormatSelection(unittest.TestCase): ] info_dict = _make_result(formats) + ydl = YDL({'format': ''}) # no criteria => anything goes + ydl.process_ie_result(info_dict.copy()) + downloaded = ydl.downloaded_info_dicts[0] + self.assertEqual(downloaded['format_id'], '35') + ydl = YDL({'format': '20/47'}) ydl.process_ie_result(info_dict.copy()) downloaded = ydl.downloaded_info_dicts[0] diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 9e5620eef..401d7c605 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -361,6 +361,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', @@ -1227,7 +1228,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]?)?) $ @@ -1309,6 +1310,9 @@ class YoutubeDL(object): '{0}\n\t{1}\n\t{2}^'.format(note, format_spec, ' ' * start[1])) return SyntaxError(message) + if not format_spec: + format_spec = 'worst' + PICKFIRST = 'PICKFIRST' MERGE = 'MERGE' SINGLE = 'SINGLE' @@ -1515,6 +1519,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'), @@ -1666,6 +1672,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