mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-21 20:46:36 -05:00
Add option --netrc-cmd
(#6682)
Authored by: NDagestad, pukkandan Closes #1706
This commit is contained in:
parent
af7585c824
commit
db3ad8a676
6 changed files with 58 additions and 28 deletions
15
README.md
15
README.md
|
@ -49,7 +49,7 @@
|
|||
* [Extractor Options](#extractor-options)
|
||||
* [CONFIGURATION](#configuration)
|
||||
* [Configuration file encoding](#configuration-file-encoding)
|
||||
* [Authentication with .netrc file](#authentication-with-netrc-file)
|
||||
* [Authentication with netrc](#authentication-with-netrc)
|
||||
* [Notes about environment variables](#notes-about-environment-variables)
|
||||
* [OUTPUT TEMPLATE](#output-template)
|
||||
* [Output template examples](#output-template-examples)
|
||||
|
@ -910,6 +910,8 @@ ## Authentication Options:
|
|||
--netrc-location PATH Location of .netrc authentication data;
|
||||
either the path or its containing directory.
|
||||
Defaults to ~/.netrc
|
||||
--netrc-cmd NETRC_CMD Command to execute to get the credentials
|
||||
credentials for an extractor.
|
||||
--video-password PASSWORD Video password (vimeo, youku)
|
||||
--ap-mso MSO Adobe Pass multiple-system operator (TV
|
||||
provider) identifier, use --ap-list-mso for
|
||||
|
@ -1203,7 +1205,7 @@ ### Configuration file encoding
|
|||
|
||||
If you want your file to be decoded differently, add `# coding: ENCODING` to the beginning of the file (e.g. `# coding: shift-jis`). There must be no characters before that, even spaces or BOM.
|
||||
|
||||
### Authentication with `.netrc` file
|
||||
### Authentication with netrc
|
||||
|
||||
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every yt-dlp execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](https://stackoverflow.com/tags/.netrc/info) on a per-extractor basis. For that you will need to create a `.netrc` file in `--netrc-location` and restrict permissions to read/write by only you:
|
||||
```
|
||||
|
@ -1223,6 +1225,15 @@ ### Authentication with `.netrc` file
|
|||
|
||||
The default location of the .netrc file is `~` (see below).
|
||||
|
||||
As an alternative to using the `.netrc` file, which has the disadvantage of keeping your passwords in a plain text file, you can configure a custom shell command to provide the credentials for an extractor. This is done by providing the `--netrc-cmd` parameter, it shall output the credentials in the netrc format and return `0` on success, other values will be treated as an error. `{}` in the command will be replaced by the name of the extractor to make it possible to select the credentials for the right extractor.
|
||||
To use braces in the command, they need to be escaped by doubling them. (see example bellow)
|
||||
|
||||
E.g. To use an encrypted `.netrc` file stored as `.authinfo.gpg`
|
||||
```
|
||||
yt-dlp --netrc-cmd 'gpg --decrypt ~/.authinfo.gpg' https://www.youtube.com/watch?v=BaW_jenozKc
|
||||
```
|
||||
|
||||
|
||||
### Notes about environment variables
|
||||
* Environment variables are normally specified as `${VARIABLE}`/`$VARIABLE` on UNIX and `%VARIABLE%` on Windows; but is always shown as `${VARIABLE}` in this documentation
|
||||
* yt-dlp also allow using UNIX-style variables on Windows for path-like options; e.g. `--output`, `--config-location`
|
||||
|
|
|
@ -190,6 +190,7 @@ class YoutubeDL:
|
|||
ap_password: Multiple-system operator account password.
|
||||
usenetrc: Use netrc for authentication instead.
|
||||
netrc_location: Location of the netrc file. Defaults to ~/.netrc.
|
||||
netrc_cmd: Use a shell command to get credentials
|
||||
verbose: Print additional info to stdout.
|
||||
quiet: Do not print messages to stdout.
|
||||
no_warnings: Do not print out anything for warnings.
|
||||
|
|
|
@ -188,8 +188,8 @@ def validate_minmax(min_val, max_val, min_name, max_name=None):
|
|||
raise ValueError(f'{max_name} "{max_val}" must be must be greater than or equal to {min_name} "{min_val}"')
|
||||
|
||||
# Usernames and passwords
|
||||
validate(not opts.usenetrc or (opts.username is None and opts.password is None),
|
||||
'.netrc', msg='using {name} conflicts with giving username/password')
|
||||
validate(sum(map(bool, (opts.usenetrc, opts.netrc_cmd, opts.username))) <= 1, '.netrc',
|
||||
msg='{name}, netrc command and username/password are mutually exclusive options')
|
||||
validate(opts.password is None or opts.username is not None, 'account username', msg='{name} missing')
|
||||
validate(opts.ap_password is None or opts.ap_username is not None,
|
||||
'TV Provider account username', msg='{name} missing')
|
||||
|
@ -741,6 +741,7 @@ def parse_options(argv=None):
|
|||
return ParsedOptions(parser, opts, urls, {
|
||||
'usenetrc': opts.usenetrc,
|
||||
'netrc_location': opts.netrc_location,
|
||||
'netrc_cmd': opts.netrc_cmd,
|
||||
'username': opts.username,
|
||||
'password': opts.password,
|
||||
'twofactor': opts.twofactor,
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
import os
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
|
@ -34,6 +35,7 @@
|
|||
GeoUtils,
|
||||
HEADRequest,
|
||||
LenientJSONDecoder,
|
||||
Popen,
|
||||
RegexNotFoundError,
|
||||
RetryManager,
|
||||
UnsupportedError,
|
||||
|
@ -70,6 +72,7 @@
|
|||
smuggle_url,
|
||||
str_or_none,
|
||||
str_to_int,
|
||||
netrc_from_content,
|
||||
strip_or_none,
|
||||
traverse_obj,
|
||||
truncate_string,
|
||||
|
@ -535,7 +538,7 @@ class InfoExtractor:
|
|||
_EMBED_REGEX = []
|
||||
|
||||
def _login_hint(self, method=NO_DEFAULT, netrc=None):
|
||||
password_hint = f'--username and --password, or --netrc ({netrc or self._NETRC_MACHINE}) to provide account credentials'
|
||||
password_hint = f'--username and --password, --netrc-cmd, or --netrc ({netrc or self._NETRC_MACHINE}) to provide account credentials'
|
||||
return {
|
||||
None: '',
|
||||
'any': f'Use --cookies, --cookies-from-browser, {password_hint}',
|
||||
|
@ -1291,45 +1294,47 @@ def _html_search_regex(self, pattern, string, name, default=NO_DEFAULT, fatal=Tr
|
|||
return clean_html(res)
|
||||
|
||||
def _get_netrc_login_info(self, netrc_machine=None):
|
||||
username = None
|
||||
password = None
|
||||
netrc_machine = netrc_machine or self._NETRC_MACHINE
|
||||
|
||||
if self.get_param('usenetrc', False):
|
||||
try:
|
||||
netrc_file = compat_expanduser(self.get_param('netrc_location') or '~')
|
||||
if os.path.isdir(netrc_file):
|
||||
netrc_file = os.path.join(netrc_file, '.netrc')
|
||||
info = netrc.netrc(file=netrc_file).authenticators(netrc_machine)
|
||||
if info is not None:
|
||||
username = info[0]
|
||||
password = info[2]
|
||||
else:
|
||||
raise netrc.NetrcParseError(
|
||||
'No authenticators for %s' % netrc_machine)
|
||||
except (OSError, netrc.NetrcParseError) as err:
|
||||
self.report_warning(
|
||||
'parsing .netrc: %s' % error_to_compat_str(err))
|
||||
cmd = self.get_param('netrc_cmd', '').format(netrc_machine)
|
||||
if cmd:
|
||||
self.to_screen(f'Executing command: {cmd}')
|
||||
stdout, _, ret = Popen.run(cmd, text=True, shell=True, stdout=subprocess.PIPE)
|
||||
if ret != 0:
|
||||
raise OSError(f'Command returned error code {ret}')
|
||||
info = netrc_from_content(stdout).authenticators(netrc_machine)
|
||||
|
||||
return username, password
|
||||
elif self.get_param('usenetrc', False):
|
||||
netrc_file = compat_expanduser(self.get_param('netrc_location') or '~')
|
||||
if os.path.isdir(netrc_file):
|
||||
netrc_file = os.path.join(netrc_file, '.netrc')
|
||||
info = netrc.netrc(netrc_file).authenticators(netrc_machine)
|
||||
|
||||
else:
|
||||
return None, None
|
||||
if not info:
|
||||
raise netrc.NetrcParseError(f'No authenticators for {netrc_machine}')
|
||||
return info[0], info[2]
|
||||
|
||||
def _get_login_info(self, username_option='username', password_option='password', netrc_machine=None):
|
||||
"""
|
||||
Get the login info as (username, password)
|
||||
First look for the manually specified credentials using username_option
|
||||
and password_option as keys in params dictionary. If no such credentials
|
||||
available look in the netrc file using the netrc_machine or _NETRC_MACHINE
|
||||
value.
|
||||
are available try the netrc_cmd if it is defined or look in the
|
||||
netrc file using the netrc_machine or _NETRC_MACHINE value.
|
||||
If there's no info available, return (None, None)
|
||||
"""
|
||||
|
||||
# Attempt to use provided username and password or .netrc data
|
||||
username = self.get_param(username_option)
|
||||
if username is not None:
|
||||
password = self.get_param(password_option)
|
||||
else:
|
||||
username, password = self._get_netrc_login_info(netrc_machine)
|
||||
|
||||
try:
|
||||
username, password = self._get_netrc_login_info(netrc_machine)
|
||||
except (OSError, netrc.NetrcParseError) as err:
|
||||
self.report_warning(f'Failed to parse .netrc: {err}')
|
||||
return None, None
|
||||
return username, password
|
||||
|
||||
def _get_tfa_info(self, note='two-factor verification code'):
|
||||
|
|
|
@ -720,6 +720,10 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs):
|
|||
'--netrc-location',
|
||||
dest='netrc_location', metavar='PATH',
|
||||
help='Location of .netrc authentication data; either the path or its containing directory. Defaults to ~/.netrc')
|
||||
authentication.add_option(
|
||||
'--netrc-cmd',
|
||||
dest='netrc_cmd', metavar='NETRC_CMD',
|
||||
help='Command to execute to get the credentials for an extractor.')
|
||||
authentication.add_option(
|
||||
'--video-password',
|
||||
dest='videopassword', metavar='PASSWORD',
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
import locale
|
||||
import math
|
||||
import mimetypes
|
||||
import netrc
|
||||
import operator
|
||||
import os
|
||||
import platform
|
||||
|
@ -864,6 +865,13 @@ def escapeHTML(text):
|
|||
)
|
||||
|
||||
|
||||
class netrc_from_content(netrc.netrc):
|
||||
def __init__(self, content):
|
||||
self.hosts, self.macros = {}, {}
|
||||
with io.StringIO(content) as stream:
|
||||
self._parse('-', stream, False)
|
||||
|
||||
|
||||
def process_communicate_or_kill(p, *args, **kwargs):
|
||||
deprecation_warning(f'"{__name__}.process_communicate_or_kill" is deprecated and may be removed '
|
||||
f'in a future version. Use "{__name__}.Popen.communicate_or_kill" instead')
|
||||
|
|
Loading…
Reference in a new issue