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.
This commit is contained in:
Jaime Marquínez Ferrándiz 2016-03-19 16:39:20 +01:00
parent fa5bda9053
commit e6f20cd142
3 changed files with 78 additions and 5 deletions

View File

@ -42,6 +42,7 @@ from .downloader import (
) )
from .extractor import gen_extractors, list_extractors from .extractor import gen_extractors, list_extractors
from .YoutubeDL import YoutubeDL from .YoutubeDL import YoutubeDL
from .params import Params, ParamsSection
def _build_ydl_opts(opts, parser): 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): def _real_main(argv=None):
# Compatibility fixes for Windows # Compatibility fixes for Windows
if sys.platform == 'win32': if sys.platform == 'win32':
@ -321,7 +346,7 @@ def _real_main(argv=None):
setproctitle('youtube-dl') setproctitle('youtube-dl')
parser, opts, args = parseOpts(argv) parser, opts, build_section_args, args = parseOpts(argv)
# Set user agent # Set user agent
if opts.user_agent is not None: if opts.user_agent is not None:
@ -385,7 +410,7 @@ def _real_main(argv=None):
compat_print(desc) compat_print(desc)
sys.exit(0) sys.exit(0)
ydl_opts = _build_ydl_opts(opts, parser) ydl_opts = CLIParams(opts, build_section_args, parser)
with YoutubeDL(ydl_opts) as ydl: with YoutubeDL(ydl_opts) as ydl:
# Update version # Update version

View File

@ -582,11 +582,17 @@ if sys.version_info >= (3, 0):
else: else:
from tokenize import generate_tokens as compat_tokenize_tokenize from tokenize import generate_tokens as compat_tokenize_tokenize
try:
import configparser as compat_configparser
except ImportError:
import ConfigParser as compat_configparser
__all__ = [ __all__ = [
'compat_HTMLParser', 'compat_HTMLParser',
'compat_HTTPError', 'compat_HTTPError',
'compat_basestring', 'compat_basestring',
'compat_chr', 'compat_chr',
'compat_configparser',
'compat_cookiejar', 'compat_cookiejar',
'compat_cookies', 'compat_cookies',
'compat_etree_fromstring', 'compat_etree_fromstring',

View File

@ -1,11 +1,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import io
import os.path import os.path
import optparse import optparse
import sys import sys
from .downloader.external import list_external_downloaders from .downloader.external import list_external_downloaders
from .compat import ( from .compat import (
compat_configparser,
compat_expanduser, compat_expanduser,
compat_get_terminal_size, compat_get_terminal_size,
compat_getenv, compat_getenv,
@ -743,17 +745,41 @@ def parseOpts(overrideArguments=None):
optionf.close() optionf.close()
return res 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(): def _readUserConf():
xdg_config_home = compat_getenv('XDG_CONFIG_HOME') xdg_config_home = compat_getenv('XDG_CONFIG_HOME')
if xdg_config_home: if xdg_config_home:
userConfFile = os.path.join(xdg_config_home, 'youtube-dl', 'config') 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): if not os.path.isfile(userConfFile):
userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf') 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: else:
userConfFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl', 'config') 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): if not os.path.isfile(userConfFile):
userConfFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl.conf') 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) userConf = _readOptions(userConfFile, None)
userIni = _readIni(userIniFile, None)
if userConf is None: if userConf is None:
appdata_dir = compat_getenv('appdata') appdata_dir = compat_getenv('appdata')
@ -778,7 +804,11 @@ def parseOpts(overrideArguments=None):
if userConf is None: if userConf is None:
userConf = [] userConf = []
return userConf if userIni is None:
userIni = [], {}
iniGlobal, iniSections = userIni
return userConf + iniGlobal, iniSections
def _hide_login_info(opts): def _hide_login_info(opts):
opts = list(opts) opts = list(opts)
@ -807,18 +837,30 @@ def parseOpts(overrideArguments=None):
if '--ignore-config' in command_line_conf: if '--ignore-config' in command_line_conf:
system_conf = [] system_conf = []
user_conf = [] user_conf = []
user_sections = {}
else: else:
system_conf = compat_conf(_readOptions('/etc/youtube-dl.conf')) system_conf = compat_conf(_readOptions('/etc/youtube-dl.conf'))
if '--ignore-config' in system_conf: if '--ignore-config' in system_conf:
user_conf = [] user_conf = []
user_sections = {}
else: 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 argv = system_conf + user_conf + command_line_conf
opts, args = parser.parse_args(argv) opts, args = parser.parse_args(argv)
if opts.verbose: if opts.verbose:
write_string('[debug] System config: ' + repr(_hide_login_info(system_conf)) + '\n') write_string('[debug] System config: ' + repr(_hide_login_info(system_conf)) + '\n')
write_string('[debug] User config: ' + repr(_hide_login_info(user_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') 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