#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2021 Mike Fährmann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. import os import sys import unittest import re import shlex sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from gallery_dl import ytdl, util, config class Test_CommandlineArguments(unittest.TestCase): module_name = "youtube_dl" @classmethod def setUpClass(cls): try: cls.module = __import__(cls.module_name) except ImportError: raise unittest.SkipTest("cannot import module '{}'".format( cls.module_name)) cls.default = ytdl.parse_command_line(cls.module, []) def test_ignore_errors(self): self._("--ignore-errors" , "ignoreerrors", True) self._("--abort-on-error", "ignoreerrors", False) def test_default_search(self): self._(["--default-search", "foo"] , "default_search", "foo") def test_mark_watched(self): self._("--mark-watched" , "mark_watched", True) self._("--no-mark-watched", "mark_watched", False) def test_proxy(self): self._(["--proxy", "socks5://127.0.0.1:1080/"], "proxy", "socks5://127.0.0.1:1080/") self._(["--cn-verification-proxy", "https://127.0.0.1"], "cn_verification_proxy", "https://127.0.0.1") self._(["--geo-verification-proxy", "127.0.0.1"], "geo_verification_proxy", "127.0.0.1") def test_retries(self): inf = float("inf") self._(["--retries", "5"], "retries", 5) self._(["--retries", "inf"], "retries", inf) self._(["--retries", "infinite"], "retries", inf) self._(["--fragment-retries", "8"], "fragment_retries", 8) self._(["--fragment-retries", "inf"], "fragment_retries", inf) self._(["--fragment-retries", "infinite"], "fragment_retries", inf) def test_geo_bypass(self): self._("--geo-bypass", "geo_bypass", True) self._("--no-geo-bypass", "geo_bypass", False) self._(["--geo-bypass-country", "EN"], "geo_bypass_country", "EN") self._(["--geo-bypass-ip-block", "198.51.100.14/24"], "geo_bypass_ip_block", "198.51.100.14/24") def test_headers(self): headers = self.module.std_headers self.assertNotEqual(headers["User-Agent"], "Foo/1.0") self._(["--user-agent", "Foo/1.0"]) self.assertEqual(headers["User-Agent"], "Foo/1.0") self.assertNotIn("Referer", headers) self._(["--referer", "http://example.org/"]) self.assertEqual(headers["Referer"], "http://example.org/") self.assertNotEqual(headers["Accept"], "*/*") self.assertNotIn("DNT", headers) self._([ "--add-header", "accept:*/*", "--add-header", "dnt:1", ]) self.assertEqual(headers["accept"], "*/*") self.assertEqual(headers["dnt"], "1") def test_extract_audio(self): opts = self._(["--extract-audio"]) self.assertEqual(opts["postprocessors"][0], { "key": "FFmpegExtractAudio", "preferredcodec": "best", "preferredquality": "5", "nopostoverwrites": False, }) opts = self._([ "--extract-audio", "--audio-format", "opus", "--audio-quality", "9", "--no-post-overwrites", ]) self.assertEqual(opts["postprocessors"][0], { "key": "FFmpegExtractAudio", "preferredcodec": "opus", "preferredquality": "9", "nopostoverwrites": True, }) def test_recode_video(self): opts = self._(["--recode-video", " mkv "]) self.assertEqual(opts["postprocessors"][0], { "key": "FFmpegVideoConvertor", "preferedformat": "mkv", }) def test_subs(self): opts = self._(["--convert-subs", "srt"]) conv = {"key": "FFmpegSubtitlesConvertor", "format": "srt"} if self.module_name == "yt_dlp": conv["when"] = "before_dl" self.assertEqual(opts["postprocessors"][0], conv) def test_embed(self): subs = {"key": "FFmpegEmbedSubtitle"} thumb = {"key": "EmbedThumbnail", "already_have_thumbnail": False} if self.module_name == "yt_dlp": subs["already_have_subtitle"] = False opts = self._(["--embed-subs", "--embed-thumbnail"]) self.assertEqual(opts["postprocessors"], [subs, thumb]) thumb["already_have_thumbnail"] = True if self.module_name == "yt_dlp": subs["already_have_subtitle"] = True opts = self._([ "--embed-thumbnail", "--embed-subs", "--write-sub", "--write-all-thumbnails", ]) self.assertEqual(opts["postprocessors"], [subs, thumb]) def test_metadata(self): opts = self._("--add-metadata") self.assertEqual(opts["postprocessors"][0], {"key": "FFmpegMetadata"}) def test_metadata_from_title(self): opts = self._(["--metadata-from-title", "%(artist)s - %(title)s"]) self.assertEqual(opts["postprocessors"][0], { "key": "MetadataFromTitle", "titleformat": "%(artist)s - %(title)s", }) def test_xattr(self): self._("--xattr-set-filesize", "xattr_set_filesize", True) opts = self._("--xattrs") self.assertEqual(opts["postprocessors"][0], {"key": "XAttrMetadata"}) def test_noop(self): result = self._([ "--update", "--dump-user-agent", "--list-extractors", "--extractor-descriptions", "--ignore-config", "--config-location", "--dump-json", "--dump-single-json", "--list-thumbnails", ]) result["daterange"] = self.default["daterange"] self.assertEqual(result, self.default) def _(self, cmdline, option=util.SENTINEL, expected=None): if isinstance(cmdline, str): cmdline = [cmdline] result = ytdl.parse_command_line(self.module, cmdline) if option is not util.SENTINEL: self.assertEqual(result[option], expected, option) return result class Test_CommandlineArguments_YtDlp(Test_CommandlineArguments): module_name = "yt_dlp" def test_retries_extractor(self): inf = float("inf") self._(["--extractor-retries", "5"], "extractor_retries", 5) self._(["--extractor-retries", "inf"], "extractor_retries", inf) self._(["--extractor-retries", "infinite"], "extractor_retries", inf) def test_remuxs_video(self): opts = self._(["--remux-video", " mkv "]) self.assertEqual(opts["postprocessors"][0], { "key": "FFmpegVideoRemuxer", "preferedformat": "mkv", }) def test_metadata(self): opts = self._(["--embed-metadata", "--no-embed-chapters", "--embed-info-json"]) self.assertEqual(opts["postprocessors"][0], { "key": "FFmpegMetadata", "add_chapters": False, "add_metadata": True, "add_infojson": True, }) def test_metadata_from_title(self): opts = self._(["--metadata-from-title", "%(artist)s - %(title)s"]) self.assertEqual(opts["postprocessors"][0], { "key": "MetadataParser", "when": "pre_process", "actions": [self.module.MetadataFromFieldPP.to_action( "title:%(artist)s - %(title)s")], }) if __name__ == "__main__": unittest.main(warnings="ignore") ''' Usage: __main__.py [OPTIONS] URL [URL...] Options: General Options: -h, --help Print this help text and exit --version Print program version and exit --force-generic-extractor Force extraction to use the generic extractor --flat-playlist Do not extract the videos of a playlist, only list them. --no-color Do not emit color codes in output Network Options: --socket-timeout SECONDS Time to wait before giving up, in seconds --source-address IP Client-side IP address to bind to -4, --force-ipv4 Make all connections via IPv4 -6, --force-ipv6 Make all connections via IPv6 Video Selection: --playlist-start NUMBER Playlist video to start at (default is 1) --playlist-end NUMBER Playlist video to end at (default is last) --playlist-items ITEM_SPEC Playlist video items to download. Specify indices of the videos in the playlist separated by commas like: "-- playlist-items 1,2,5,8" if you want to download videos indexed 1, 2, 5, 8 in the playlist. You can specify range: " --playlist-items 1-3,7,10-13", it will download the videos at index 1, 2, 3, 7, 10, 11, 12 and 13. --match-title REGEX Download only matching titles (regex or caseless sub-string) --reject-title REGEX Skip download for matching titles (regex or caseless sub-string) --max-downloads NUMBER Abort after downloading NUMBER files --min-filesize SIZE Do not download any videos smaller than SIZE (e.g. 50k or 44.6m) --max-filesize SIZE Do not download any videos larger than SIZE (e.g. 50k or 44.6m) --date DATE Download only videos uploaded in this date --datebefore DATE Download only videos uploaded on or before this date (i.e. inclusive) --dateafter DATE Download only videos uploaded on or after this date (i.e. inclusive) --min-views COUNT Do not download any videos with less than COUNT views --max-views COUNT Do not download any videos with more than COUNT views --match-filter FILTER Generic video filter. Specify any key (see the "OUTPUT TEMPLATE" for a list of available keys) to match if the key is present, !key to check if the key is not present, key > NUMBER (like "comment_count > 12", also works with >=, <, <=, !=, =) to compare against a number, key = 'LITERAL' (like "uploader = 'Mike Smith'", also works with !=) to match against a string literal and & to require multiple matches. Values which are not known are excluded unless you put a question mark (?) after the operator. For example, to only match videos that have been liked more than 100 times and disliked less than 50 times (or the dislike functionality is not available at the given service), but who also have a description, use --match-filter "like_count > 100 & dislike_count