From e6f20cd14253bfb1774027f1c76f5edf66ede640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Marqui=CC=81nez=20Ferra=CC=81ndiz?= Date: Sat, 19 Mar 2016 16:39:20 +0100 Subject: [PATCH] Add support for loading a config file with different parameters for each site On Unix it look for ~/.config/youtube-dl/config.ini or ~/.config/youtube-dl.ini, Windows support is currently missing. The new config file is in the INI format, the options are given in the long form without the leading dashes. Parameters for an specific extractor are written in their own section. When a video is contained in a playlist, it inherits the parameters from the playlist extractor. Example: write-thumbnail= [youtube:playlist] output=%(playlist)s/%(id)s.%(ext)s [youtube] format=webm All the videos use '--write-thumbnail', videos inside a YouTube playlist use the specified output template and YouTube videos are downloaded in the webm format. --- youtube_dl/__init__.py | 29 +++++++++++++++++++++++-- youtube_dl/compat.py | 6 ++++++ youtube_dl/options.py | 48 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 78 insertions(+), 5 deletions(-) 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