diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 0b26e7047..e78b49236 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -42,6 +42,7 @@ from .downloader import ( ) from .extractor import gen_extractors, list_extractors from .YoutubeDL import YoutubeDL +from .params import Params, ParamsSection def _build_ydl_opts(opts, parser): @@ -311,6 +312,30 @@ def _build_ydl_opts(opts, parser): } +class CLIParams(Params): + def __init__(self, opts, build_section_args, parser): + super(CLIParams, self).__init__(_build_ydl_opts(opts, parser)) + self.build_section_args = build_section_args + self.parser = parser + + def section(self, section): + return CLIParamsSection([section], self.build_section_args, self.parser) + + +# TODO: investigate if it's worth to cache this +class CLIParamsSection(ParamsSection): + def __init__(self, sections, build_section_args, parser): + section_args = build_section_args(*sections) + section_opts = parser.parse_args(section_args)[0] + super(CLIParamsSection, self).__init__(_build_ydl_opts(section_opts, parser), {}) + self.section_names = sections + self.build_section_args = build_section_args + self.parser = parser + + def section(self, section): + return CLIParamsSection(self.section_names + [section], self.build_section_args, self.parser) + + def _real_main(argv=None): # Compatibility fixes for Windows if sys.platform == 'win32': @@ -321,7 +346,7 @@ def _real_main(argv=None): setproctitle('youtube-dl') - parser, opts, args = parseOpts(argv) + parser, opts, build_section_args, args = parseOpts(argv) # Set user agent if opts.user_agent is not None: @@ -385,7 +410,7 @@ def _real_main(argv=None): compat_print(desc) sys.exit(0) - ydl_opts = _build_ydl_opts(opts, parser) + ydl_opts = CLIParams(opts, build_section_args, parser) with YoutubeDL(ydl_opts) as ydl: # Update version diff --git a/youtube_dl/compat.py b/youtube_dl/compat.py index 76b6b0e38..feb52fd56 100644 --- a/youtube_dl/compat.py +++ b/youtube_dl/compat.py @@ -582,11 +582,17 @@ if sys.version_info >= (3, 0): else: from tokenize import generate_tokens as compat_tokenize_tokenize +try: + import configparser as compat_configparser +except ImportError: + import ConfigParser as compat_configparser + __all__ = [ 'compat_HTMLParser', 'compat_HTTPError', 'compat_basestring', 'compat_chr', + 'compat_configparser', 'compat_cookiejar', 'compat_cookies', 'compat_etree_fromstring', diff --git a/youtube_dl/options.py b/youtube_dl/options.py index 4f9e40c9d..cbc752190 100644 --- a/youtube_dl/options.py +++ b/youtube_dl/options.py @@ -1,11 +1,13 @@ from __future__ import unicode_literals +import io import os.path import optparse import sys from .downloader.external import list_external_downloaders from .compat import ( + compat_configparser, compat_expanduser, compat_get_terminal_size, compat_getenv, @@ -743,17 +745,41 @@ def parseOpts(overrideArguments=None): optionf.close() return res + def _readIni(filename_bytes, default=([], {})): + try: + ini = open(filename_bytes) + except IOError: + return default # silently skip if file is not present + parser = compat_configparser.RawConfigParser() + ini = io.StringIO('[@GLOBAL@]\n' + ini.read()) + parser.readfp(ini) + + def convert_opts(opts): + return [ + '--' + opt + ('' if not arg else ('=' + arg)) + for opt, arg in opts] + global_opts = convert_opts(parser.items('@GLOBAL@')) + section_opts = dict((section, convert_opts(parser.items(section))) for section in parser.sections() if section != '@GLOBAL@') + return global_opts, section_opts + def _readUserConf(): xdg_config_home = compat_getenv('XDG_CONFIG_HOME') if xdg_config_home: userConfFile = os.path.join(xdg_config_home, 'youtube-dl', 'config') + userIniFile = os.path.join(xdg_config_home, 'youtube-dl', 'config.ini') if not os.path.isfile(userConfFile): userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf') + if not os.path.isfile(userIniFile): + userIniFile = os.path.join(xdg_config_home, 'youtube-dl.ini') else: userConfFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl', 'config') + userIniFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl', 'config.ini') if not os.path.isfile(userConfFile): userConfFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl.conf') + if not os.path.isfile(userIniFile): + userIniFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl.ini') userConf = _readOptions(userConfFile, None) + userIni = _readIni(userIniFile, None) if userConf is None: appdata_dir = compat_getenv('appdata') @@ -778,7 +804,11 @@ def parseOpts(overrideArguments=None): if userConf is None: userConf = [] - return userConf + if userIni is None: + userIni = [], {} + iniGlobal, iniSections = userIni + + return userConf + iniGlobal, iniSections def _hide_login_info(opts): opts = list(opts) @@ -807,18 +837,30 @@ def parseOpts(overrideArguments=None): if '--ignore-config' in command_line_conf: system_conf = [] user_conf = [] + user_sections = {} else: system_conf = compat_conf(_readOptions('/etc/youtube-dl.conf')) if '--ignore-config' in system_conf: user_conf = [] + user_sections = {} else: - user_conf = compat_conf(_readUserConf()) + user_conf, user_sections = _readUserConf() + user_conf = compat_conf(user_conf) argv = system_conf + user_conf + command_line_conf opts, args = parser.parse_args(argv) if opts.verbose: write_string('[debug] System config: ' + repr(_hide_login_info(system_conf)) + '\n') write_string('[debug] User config: ' + repr(_hide_login_info(user_conf)) + '\n') + for section, section_args in user_sections.items(): + write_string('[debug] User config for "' + section + '": ' + repr(_hide_login_info(section_args)) + '\n') write_string('[debug] Command-line args: ' + repr(_hide_login_info(command_line_conf)) + '\n') - return parser, opts, args + def build_section_args(*sections): + res = system_conf + user_conf + for section in sections: + res.extend(user_sections.get(section, [])) + res.extend(command_line_conf) + return res + + return parser, opts, build_section_args, args