mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-31 06:35:12 +00:00 
			
		
		
		
	Add option --netrc-cmd (#6682)
				
					
				
			Authored by: NDagestad, pukkandan Closes #1706
This commit is contained in:
		
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								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) |     * [Extractor Options](#extractor-options) | ||||||
| * [CONFIGURATION](#configuration) | * [CONFIGURATION](#configuration) | ||||||
|     * [Configuration file encoding](#configuration-file-encoding) |     * [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) |     * [Notes about environment variables](#notes-about-environment-variables) | ||||||
| * [OUTPUT TEMPLATE](#output-template) | * [OUTPUT TEMPLATE](#output-template) | ||||||
|     * [Output template examples](#output-template-examples) |     * [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; |     --netrc-location PATH           Location of .netrc authentication data; | ||||||
|                                     either the path or its containing directory. |                                     either the path or its containing directory. | ||||||
|                                     Defaults to ~/.netrc |                                     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) |     --video-password PASSWORD       Video password (vimeo, youku) | ||||||
|     --ap-mso MSO                    Adobe Pass multiple-system operator (TV |     --ap-mso MSO                    Adobe Pass multiple-system operator (TV | ||||||
|                                     provider) identifier, use --ap-list-mso for |                                     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. | 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: | 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). | 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 | ### 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 | * 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` | * 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. |     ap_password:       Multiple-system operator account password. | ||||||
|     usenetrc:          Use netrc for authentication instead. |     usenetrc:          Use netrc for authentication instead. | ||||||
|     netrc_location:    Location of the netrc file. Defaults to ~/.netrc. |     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. |     verbose:           Print additional info to stdout. | ||||||
|     quiet:             Do not print messages to stdout. |     quiet:             Do not print messages to stdout. | ||||||
|     no_warnings:       Do not print out anything for warnings. |     no_warnings:       Do not print out anything for warnings. | ||||||
|   | |||||||
| @@ -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}"') |         raise ValueError(f'{max_name} "{max_val}" must be must be greater than or equal to {min_name} "{min_val}"') | ||||||
| 
 | 
 | ||||||
|     # Usernames and passwords |     # Usernames and passwords | ||||||
|     validate(not opts.usenetrc or (opts.username is None and opts.password is None), |     validate(sum(map(bool, (opts.usenetrc, opts.netrc_cmd, opts.username))) <= 1, '.netrc', | ||||||
|              '.netrc', msg='using {name} conflicts with giving username/password') |              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.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, |     validate(opts.ap_password is None or opts.ap_username is not None, | ||||||
|              'TV Provider account username', msg='{name} missing') |              'TV Provider account username', msg='{name} missing') | ||||||
| @@ -741,6 +741,7 @@ def parse_options(argv=None): | |||||||
|     return ParsedOptions(parser, opts, urls, { |     return ParsedOptions(parser, opts, urls, { | ||||||
|         'usenetrc': opts.usenetrc, |         'usenetrc': opts.usenetrc, | ||||||
|         'netrc_location': opts.netrc_location, |         'netrc_location': opts.netrc_location, | ||||||
|  |         'netrc_cmd': opts.netrc_cmd, | ||||||
|         'username': opts.username, |         'username': opts.username, | ||||||
|         'password': opts.password, |         'password': opts.password, | ||||||
|         'twofactor': opts.twofactor, |         'twofactor': opts.twofactor, | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import netrc | |||||||
| import os | import os | ||||||
| import random | import random | ||||||
| import re | import re | ||||||
|  | import subprocess | ||||||
| import sys | import sys | ||||||
| import time | import time | ||||||
| import types | import types | ||||||
| @@ -34,6 +35,7 @@ from ..utils import ( | |||||||
|     GeoUtils, |     GeoUtils, | ||||||
|     HEADRequest, |     HEADRequest, | ||||||
|     LenientJSONDecoder, |     LenientJSONDecoder, | ||||||
|  |     Popen, | ||||||
|     RegexNotFoundError, |     RegexNotFoundError, | ||||||
|     RetryManager, |     RetryManager, | ||||||
|     UnsupportedError, |     UnsupportedError, | ||||||
| @@ -70,6 +72,7 @@ from ..utils import ( | |||||||
|     smuggle_url, |     smuggle_url, | ||||||
|     str_or_none, |     str_or_none, | ||||||
|     str_to_int, |     str_to_int, | ||||||
|  |     netrc_from_content, | ||||||
|     strip_or_none, |     strip_or_none, | ||||||
|     traverse_obj, |     traverse_obj, | ||||||
|     truncate_string, |     truncate_string, | ||||||
| @@ -535,7 +538,7 @@ class InfoExtractor: | |||||||
|     _EMBED_REGEX = [] |     _EMBED_REGEX = [] | ||||||
| 
 | 
 | ||||||
|     def _login_hint(self, method=NO_DEFAULT, netrc=None): |     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 { |         return { | ||||||
|             None: '', |             None: '', | ||||||
|             'any': f'Use --cookies, --cookies-from-browser, {password_hint}', |             'any': f'Use --cookies, --cookies-from-browser, {password_hint}', | ||||||
| @@ -1291,45 +1294,47 @@ class InfoExtractor: | |||||||
|         return clean_html(res) |         return clean_html(res) | ||||||
| 
 | 
 | ||||||
|     def _get_netrc_login_info(self, netrc_machine=None): |     def _get_netrc_login_info(self, netrc_machine=None): | ||||||
|         username = None |  | ||||||
|         password = None |  | ||||||
|         netrc_machine = netrc_machine or self._NETRC_MACHINE |         netrc_machine = netrc_machine or self._NETRC_MACHINE | ||||||
| 
 | 
 | ||||||
|         if self.get_param('usenetrc', False): |         cmd = self.get_param('netrc_cmd', '').format(netrc_machine) | ||||||
|             try: |         if cmd: | ||||||
|                 netrc_file = compat_expanduser(self.get_param('netrc_location') or '~') |             self.to_screen(f'Executing command: {cmd}') | ||||||
|                 if os.path.isdir(netrc_file): |             stdout, _, ret = Popen.run(cmd, text=True, shell=True, stdout=subprocess.PIPE) | ||||||
|                     netrc_file = os.path.join(netrc_file, '.netrc') |             if ret != 0: | ||||||
|                 info = netrc.netrc(file=netrc_file).authenticators(netrc_machine) |                 raise OSError(f'Command returned error code {ret}') | ||||||
|                 if info is not None: |             info = netrc_from_content(stdout).authenticators(netrc_machine) | ||||||
|                     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)) |  | ||||||
| 
 | 
 | ||||||
|         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): |     def _get_login_info(self, username_option='username', password_option='password', netrc_machine=None): | ||||||
|         """ |         """ | ||||||
|         Get the login info as (username, password) |         Get the login info as (username, password) | ||||||
|         First look for the manually specified credentials using username_option |         First look for the manually specified credentials using username_option | ||||||
|         and password_option as keys in params dictionary. If no such credentials |         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 |         are available try the netrc_cmd if it is defined or look in the | ||||||
|         value. |         netrc file using the netrc_machine or _NETRC_MACHINE value. | ||||||
|         If there's no info available, return (None, None) |         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) |         username = self.get_param(username_option) | ||||||
|         if username is not None: |         if username is not None: | ||||||
|             password = self.get_param(password_option) |             password = self.get_param(password_option) | ||||||
|         else: |         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 |         return username, password | ||||||
| 
 | 
 | ||||||
|     def _get_tfa_info(self, note='two-factor verification code'): |     def _get_tfa_info(self, note='two-factor verification code'): | ||||||
|   | |||||||
| @@ -720,6 +720,10 @@ def create_parser(): | |||||||
|         '--netrc-location', |         '--netrc-location', | ||||||
|         dest='netrc_location', metavar='PATH', |         dest='netrc_location', metavar='PATH', | ||||||
|         help='Location of .netrc authentication data; either the path or its containing directory. Defaults to ~/.netrc') |         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( |     authentication.add_option( | ||||||
|         '--video-password', |         '--video-password', | ||||||
|         dest='videopassword', metavar='PASSWORD', |         dest='videopassword', metavar='PASSWORD', | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ import json | |||||||
| import locale | import locale | ||||||
| import math | import math | ||||||
| import mimetypes | import mimetypes | ||||||
|  | import netrc | ||||||
| import operator | import operator | ||||||
| import os | import os | ||||||
| import platform | 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): | def process_communicate_or_kill(p, *args, **kwargs): | ||||||
|     deprecation_warning(f'"{__name__}.process_communicate_or_kill" is deprecated and may be removed ' |     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') |                         f'in a future version. Use "{__name__}.Popen.communicate_or_kill" instead') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Nicolai Dagestad
					Nicolai Dagestad