aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolai Dagestad <nicolai.github@dagestad.fr>2023-06-21 05:07:42 +0200
committerGitHub <noreply@github.com>2023-06-21 08:37:42 +0530
commitdb3ad8a67661d7b234a6954d9c6a4a9b1749f5eb (patch)
treef3ad8c554513bd06901ae78b2642c9a1ff407d81
parentaf7585c824a1e405bd8afa46d87b4be322edc93c (diff)
downloadhypervideo-pre-db3ad8a67661d7b234a6954d9c6a4a9b1749f5eb.tar.lz
hypervideo-pre-db3ad8a67661d7b234a6954d9c6a4a9b1749f5eb.tar.xz
hypervideo-pre-db3ad8a67661d7b234a6954d9c6a4a9b1749f5eb.zip
Add option `--netrc-cmd` (#6682)
Authored by: NDagestad, pukkandan Closes #1706
-rw-r--r--README.md15
-rw-r--r--yt_dlp/YoutubeDL.py1
-rw-r--r--yt_dlp/__init__.py5
-rw-r--r--yt_dlp/extractor/common.py53
-rw-r--r--yt_dlp/options.py4
-rw-r--r--yt_dlp/utils/_utils.py8
6 files changed, 58 insertions, 28 deletions
diff --git a/README.md b/README.md
index 578f84956..9a00da903 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on t
* [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 @@ If you fork the project on GitHub, you can run your fork's [build workflow](.git
--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 @@ The configuration files are decoded according to the UTF BOM if present, and in
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 @@ To activate authentication with the `.netrc` file you should pass `--netrc` to y
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`
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index a546ce65b..e51bceef3 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -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.
diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py
index 137c9503f..46edd88d3 100644
--- a/yt_dlp/__init__.py
+++ b/yt_dlp/__init__.py
@@ -188,8 +188,8 @@ def validate_options(opts):
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,
diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py
index ca2164a5d..f11a67358 100644
--- a/yt_dlp/extractor/common.py
+++ b/yt_dlp/extractor/common.py
@@ -13,6 +13,7 @@ import netrc
import os
import random
import re
+import subprocess
import sys
import time
import types
@@ -34,6 +35,7 @@ from ..utils import (
GeoUtils,
HEADRequest,
LenientJSONDecoder,
+ Popen,
RegexNotFoundError,
RetryManager,
UnsupportedError,
@@ -70,6 +72,7 @@ from ..utils import (
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 @@ class InfoExtractor:
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'):
diff --git a/yt_dlp/options.py b/yt_dlp/options.py
index 1c8d73f16..b174a24af 100644
--- a/yt_dlp/options.py
+++ b/yt_dlp/options.py
@@ -721,6 +721,10 @@ def create_parser():
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',
help='Video password (vimeo, youku)')
diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py
index d10d621d5..28c2785cb 100644
--- a/yt_dlp/utils/_utils.py
+++ b/yt_dlp/utils/_utils.py
@@ -25,6 +25,7 @@ import json
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')