From 78a39f065a474408130b21212f621ad7ed24f30f Mon Sep 17 00:00:00 2001 From: Brian Le Date: Sun, 8 Sep 2024 21:58:30 -0700 Subject: [PATCH 1/9] Plugin error handling --- README.md | 2 +- .../yt_dlp_plugins/extractor/getpot_bgutil.py | 39 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2ffd524..c68abae 100644 --- a/README.md +++ b/README.md @@ -99,5 +99,5 @@ If using option (a) HTTP Server for the provider, use yt-dlp like normal 🙂. If using option (b) script for the provider, you need to pass extractor arguments including the path to the generation script for each yt-dlp call. Make sure to point to the transpiled version, `server/build/generate_once.js` ```shell -./yt-dlp --extractor-args "youtube:getpot_bgutil_script=/home/user/bgutil-test/bgutil-ytdlp-pot-provider/server/build/generate_once.js" +./yt-dlp --extractor-args "youtube:getpot_bgutil_script=/home/user/bgutil-ytdlp-pot-provider/server/build/generate_once.js" ``` diff --git a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py index 14c76af..6505fcf 100644 --- a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py +++ b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py @@ -1,5 +1,7 @@ import json import subprocess +import os.path +import shutil from yt_dlp import YoutubeDL from yt_dlp.networking.common import Request @@ -27,22 +29,35 @@ def _get_pot(self, client: str, ydl: YoutubeDL, visitor_data=None, data_sync_id= po_token = self._get_pot_via_http(ydl, client, visitor_data, data_sync_id) return po_token - - def _get_pot_via_http(self, ydl, client, visitor_data, data_sync_id): - response = ydl.urlopen(Request('http://127.0.0.1:4416/get_pot', data=json.dumps({ - 'client': client, - 'visitor_data': visitor_data, - 'data_sync_id': data_sync_id - }).encode(), headers = {'Content-Type': 'application/json'})) - - response_json = json.loads(response.read().decode('utf-8')) + def _get_pot_via_http(self, ydl, client, visitor_data, data_sync_id): + try: + response = ydl.urlopen(Request('http://127.0.0.1:4416/get_pot', data=json.dumps({ + 'client': client, + 'visitor_data': visitor_data, + 'data_sync_id': data_sync_id + }).encode(), headers = {'Content-Type': 'application/json'})) + except Exception as e: + raise RequestError(f"Error reaching POST /get_pot: {str(e)}") + + try: + response_json = json.loads(response.read().decode('utf-8')) + except Exception as e: + raise RequestError(f"Error parsing response JSON: {str(e)}. response = {response.read().decode('utf-8')}") + if 'po_token' not in response_json: raise RequestError('Server did not respond with a po_token') return response_json["po_token"] def _get_pot_via_script(self, script_path, visitor_data, data_sync_id): + if not os.path.isfile(script_path): + raise RequestError(f"Script path doesn't exist: {script_path}") + if os.path.basename(script_path) != 'generate_once.js': + raise RequestError(f"Incorrect script passed to extractor args. Path to generate_once.js required") + if shutil.which('node') is None: + raise RequestError(f"node is not in PATH") + # possibly vulnerable to shell injection here? but risk is low command_args = ['node', script_path] if data_sync_id: @@ -62,4 +77,8 @@ def _get_pot_via_script(self, script_path, visitor_data, data_sync_id): # the JSON response is always the last line script_data_resp = result.stdout.splitlines()[-1] self._logger.debug(f"_get_pot_via_script response = {script_data_resp}") - return json.loads(script_data_resp)['poToken'] + try: + return json.loads(script_data_resp)['poToken'] + except: + raise RequestError("Error parsing JSON response from _get_pot_via_script") + From 8eb6db2f46cf63eec70f55072859d949a8d2daa4 Mon Sep 17 00:00:00 2001 From: N/Ame <173015200+grqz@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:10:06 +0000 Subject: [PATCH 2/9] [plugin/misc] cleanup * allow custom http server base url by passing argument `getpot_bgutil_baseurl` (defaults to `http://127.0.0.1:4416`) * Code formatting(use single quotes): be consistent, remove trailing whitespaces * Use `yt_dlp.utils.Popen` instead of `subprocess.Popen` * Add cause to several `RequestError`'s * Do not use bare `except` [server] add options to main.ts, misc * generate_once.ts: Remove repeated logging * main.ts: allow custom http server port by passing `-p, --port ` (defaults to 4416) * main.ts: allow verbose logging for `SessionManager` by passing argument `--verbose` * main.ts: return a JSON object when it fails to generate a visitordata --- .../yt_dlp_plugins/extractor/getpot_bgutil.py | 65 ++++++++++--------- server/src/generate_once.ts | 3 - server/src/main.ts | 14 +++- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py index 6505fcf..ec0de39 100644 --- a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py +++ b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py @@ -6,7 +6,8 @@ from yt_dlp.networking.common import Request from yt_dlp.networking.exceptions import RequestError, UnsupportedRequest -from yt_dlp_plugins.extractor.getpot import GetPOTProvider, register_provider, register_preference +from yt_dlp.utils import Popen +from yt_dlp_plugins.extractor.getpot import GetPOTProvider, register_provider @register_provider @@ -20,65 +21,71 @@ def _validate_get_pot(self, client: str, ydl: YoutubeDL, visitor_data=None, data def _get_pot(self, client: str, ydl: YoutubeDL, visitor_data=None, data_sync_id=None, player_url=None, **kwargs) -> str: generate_pot_script_path = ydl.get_info_extractor('Youtube')._configuration_arg('getpot_bgutil_script', [None], casesense=True)[0] + http_base_url = ydl.get_info_extractor('Youtube')._configuration_arg('getpot_bgutil_baseurl', ['http://127.0.0.1:4416'], casesense=True)[0] if generate_pot_script_path: - self._logger.info(f"Generating POT via script: {generate_pot_script_path}") + self._logger.info(f'Generating POT via script: {generate_pot_script_path}') po_token = self._get_pot_via_script(generate_pot_script_path, visitor_data, data_sync_id) - return po_token else: - self._logger.info(f"Generating POT via HTTP server") - po_token = self._get_pot_via_http(ydl, client, visitor_data, data_sync_id) + self._logger.info('Generating POT via HTTP server') + po_token = self._get_pot_via_http(ydl, client, visitor_data, data_sync_id, http_base_url) return po_token - def _get_pot_via_http(self, ydl, client, visitor_data, data_sync_id): + def _get_pot_via_http(self, ydl, client, visitor_data, data_sync_id, base_url): try: response = ydl.urlopen(Request('http://127.0.0.1:4416/get_pot', data=json.dumps({ 'client': client, 'visitor_data': visitor_data, 'data_sync_id': data_sync_id - }).encode(), headers = {'Content-Type': 'application/json'})) + }).encode(), headers={'Content-Type': 'application/json'})) except Exception as e: - raise RequestError(f"Error reaching POST /get_pot: {str(e)}") - + raise RequestError(f'Error reaching POST /get_pot: {str(e)}') + try: response_json = json.loads(response.read().decode('utf-8')) except Exception as e: - raise RequestError(f"Error parsing response JSON: {str(e)}. response = {response.read().decode('utf-8')}") - + raise RequestError(f'Error parsing response JSON. response = {response.read().decode("utf-8")}', cause=e) + + if error_msg := response_json.get('error'): + raise RequestError(error_msg) if 'po_token' not in response_json: raise RequestError('Server did not respond with a po_token') - return response_json["po_token"] + return response_json['po_token'] def _get_pot_via_script(self, script_path, visitor_data, data_sync_id): if not os.path.isfile(script_path): raise RequestError(f"Script path doesn't exist: {script_path}") if os.path.basename(script_path) != 'generate_once.js': - raise RequestError(f"Incorrect script passed to extractor args. Path to generate_once.js required") + raise RequestError('Incorrect script passed to extractor args. Path to generate_once.js required') if shutil.which('node') is None: - raise RequestError(f"node is not in PATH") + raise RequestError('node is not in PATH') # possibly vulnerable to shell injection here? but risk is low command_args = ['node', script_path] if data_sync_id: - command_args.extend(["-d", data_sync_id]) + command_args.extend(['-d', data_sync_id]) elif visitor_data: - command_args.extend(["-v", visitor_data]) + command_args.extend(['-v', visitor_data]) else: - raise RequestError("Unexpected missing visitorData/dataSyncId in _get_pot_via_script") - self._logger.debug(f"Executing command to get POT via script: {' '.join(command_args)}") + raise RequestError('Unexpected missing visitorData/dataSyncId in _get_pot_via_script') + self._logger.debug(f'Executing command to get POT via script: {" ".join(command_args)}') + + try: + stdout, stderr, returncode = Popen.run( + command_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + except Exception as e: + raise RequestError('_get_pot_via_script failed: Unable to run script', cause=e) + + self._logger.debug(f'stdout = {stdout}') + if returncode: + raise RequestError( + f'_get_pot_via_script failed with returncode {returncode}:\n{stderr.strip()}') - result = subprocess.run(command_args,stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - - self._logger.debug(f"stdout = {result.stdout}") - if result.stderr or result.returncode != 0: - raise RequestError(f"_get_pot_via_script failed with return code {result.returncode}. stderr = {result.stderr}") - # the JSON response is always the last line - script_data_resp = result.stdout.splitlines()[-1] - self._logger.debug(f"_get_pot_via_script response = {script_data_resp}") + script_data_resp = stdout.splitlines()[-1] + self._logger.debug(f'_get_pot_via_script response = {script_data_resp}') try: return json.loads(script_data_resp)['poToken'] - except: - raise RequestError("Error parsing JSON response from _get_pot_via_script") - + except (json.JSONDecodeError, TypeError, KeyError) as e: + raise RequestError('Error parsing JSON response from _get_pot_via_script', cause=e) diff --git a/server/src/generate_once.ts b/server/src/generate_once.ts index 96093b6..38735fa 100644 --- a/server/src/generate_once.ts +++ b/server/src/generate_once.ts @@ -39,9 +39,6 @@ const options = program.opts(); } if (verbose) { - console.log( - `Received request for visitor data, grabbing from Innertube`, - ); console.log(`Generated visitor data: ${generatedVisitorData}`); } diff --git a/server/src/main.ts b/server/src/main.ts index 336b539..ef05edd 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,8 +1,16 @@ import { SessionManager } from "./session_manager"; +import { Command } from "@commander-js/extra-typings"; import express from "express"; import bodyParser from "body-parser"; -const PORT_NUMBER = 4416; +const program = new Command() + .option("-p, --port ") + .option("--verbose"); + +program.parse(); +const options = program.opts(); + +const PORT_NUMBER = options.port || 4416; const httpServer = express(); httpServer.use(bodyParser.json()); @@ -14,7 +22,7 @@ httpServer.listen({ console.log(`Started POT server on port ${PORT_NUMBER}`); -const sessionManager = new SessionManager(); +const sessionManager = new SessionManager(options.verbose || false); httpServer.post("/get_pot", async (request, response) => { const visitorData = request.body.visitor_data as string; const dataSyncId = request.body.data_sync_id as string; @@ -36,7 +44,7 @@ httpServer.post("/get_pot", async (request, response) => { const generatedVisitorData = await sessionManager.generateVisitorData(); if (!generatedVisitorData) { response.status(500); - response.send("Error generating visitor data"); + response.send({error: "Error generating visitor data"}); return; } From b7081b91d153b8055654e9387b9d0f86494f81d9 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Mon, 9 Sep 2024 10:52:12 -0700 Subject: [PATCH 3/9] Update README.md Co-authored-by: N/Ame <173015200+grqz@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c68abae..f338cc9 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ This will automatically install [coletdjnz's POT plugin framework](https://githu If using option (a) HTTP Server for the provider, use yt-dlp like normal 🙂. -If using option (b) script for the provider, you need to pass extractor arguments including the path to the generation script for each yt-dlp call. Make sure to point to the transpiled version, `server/build/generate_once.js` +If using option (b) script for the provider, you need to pass the extractor argument `getpot_bgutil_script` to `youtube` for each yt-dlp call. The argument should include the path to the transpiled generation script (`server/build/generate_once.js`). E.g. `--extractor-args "youtube:getpot_bgutil_script=$WORKSPACE/bgutil-ytdlp-pot-provider/server/build/generate_once.js"` ```shell ./yt-dlp --extractor-args "youtube:getpot_bgutil_script=/home/user/bgutil-ytdlp-pot-provider/server/build/generate_once.js" From e1f77113aecf4fb8f2c8dd97fa47792e04373a2b Mon Sep 17 00:00:00 2001 From: Brian Le Date: Mon, 9 Sep 2024 10:52:31 -0700 Subject: [PATCH 4/9] Remove redundant code snippet --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index f338cc9..42d649b 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,3 @@ This will automatically install [coletdjnz's POT plugin framework](https://githu If using option (a) HTTP Server for the provider, use yt-dlp like normal 🙂. If using option (b) script for the provider, you need to pass the extractor argument `getpot_bgutil_script` to `youtube` for each yt-dlp call. The argument should include the path to the transpiled generation script (`server/build/generate_once.js`). E.g. `--extractor-args "youtube:getpot_bgutil_script=$WORKSPACE/bgutil-ytdlp-pot-provider/server/build/generate_once.js"` - -```shell -./yt-dlp --extractor-args "youtube:getpot_bgutil_script=/home/user/bgutil-ytdlp-pot-provider/server/build/generate_once.js" -``` From 9acc0f698cfa20d3af6fd84ff07b3a0d99e703eb Mon Sep 17 00:00:00 2001 From: Brian Le Date: Mon, 9 Sep 2024 16:54:49 -0700 Subject: [PATCH 5/9] Update plugin/yt_dlp_plugins/extractor/getpot_bgutil.py Co-authored-by: N/Ame <173015200+grqz@users.noreply.github.com> --- plugin/yt_dlp_plugins/extractor/getpot_bgutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py index ec0de39..59b1c07 100644 --- a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py +++ b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py @@ -33,7 +33,7 @@ def _get_pot(self, client: str, ydl: YoutubeDL, visitor_data=None, data_sync_id= def _get_pot_via_http(self, ydl, client, visitor_data, data_sync_id, base_url): try: - response = ydl.urlopen(Request('http://127.0.0.1:4416/get_pot', data=json.dumps({ + response = ydl.urlopen(Request(f'{base_url}/get_pot', data=json.dumps({ 'client': client, 'visitor_data': visitor_data, 'data_sync_id': data_sync_id From d2682e9f295dd3ec3e14d05d3c412bc9731e8fc3 Mon Sep 17 00:00:00 2001 From: N/Ame <173015200+grqz@users.noreply.github.com> Date: Tue, 10 Sep 2024 06:39:37 +0000 Subject: [PATCH 6/9] [plugin] cleanup * code formatting * be consistent(`encode()`, `decode()`): remove redundant arg `'utf-8'` * be consistent: deal with invalid parameter (i.e. `base_url is None`) inside the function * use `json.load` to read response * no longer passes `cause` to the `RequestError` constructor as it is useless when a message is present, move the cause to the message * logging: improve clarity --- .../yt_dlp_plugins/extractor/getpot_bgutil.py | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py index 59b1c07..2ede751 100644 --- a/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py +++ b/plugin/yt_dlp_plugins/extractor/getpot_bgutil.py @@ -17,34 +17,43 @@ class BgUtilPotProviderRH(GetPOTProvider): def _validate_get_pot(self, client: str, ydl: YoutubeDL, visitor_data=None, data_sync_id=None, player_url=None, **kwargs): if not data_sync_id and not visitor_data: - raise UnsupportedRequest('One of [data_sync_id, visitor_data] must be passed') + raise UnsupportedRequest( + 'One of [data_sync_id, visitor_data] must be passed') def _get_pot(self, client: str, ydl: YoutubeDL, visitor_data=None, data_sync_id=None, player_url=None, **kwargs) -> str: - generate_pot_script_path = ydl.get_info_extractor('Youtube')._configuration_arg('getpot_bgutil_script', [None], casesense=True)[0] - http_base_url = ydl.get_info_extractor('Youtube')._configuration_arg('getpot_bgutil_baseurl', ['http://127.0.0.1:4416'], casesense=True)[0] + generate_pot_script_path = ydl.get_info_extractor('Youtube')._configuration_arg( + 'getpot_bgutil_script', [None], casesense=True)[0] + http_base_url = ydl.get_info_extractor('Youtube')._configuration_arg( + 'getpot_bgutil_baseurl', [None], casesense=True)[0] if generate_pot_script_path: - self._logger.info(f'Generating POT via script: {generate_pot_script_path}') - po_token = self._get_pot_via_script(generate_pot_script_path, visitor_data, data_sync_id) + self._logger.info( + f'Generating POT via script: {generate_pot_script_path}') + po_token = self._get_pot_via_script( + generate_pot_script_path, visitor_data, data_sync_id) else: self._logger.info('Generating POT via HTTP server') - po_token = self._get_pot_via_http(ydl, client, visitor_data, data_sync_id, http_base_url) + po_token = self._get_pot_via_http( + ydl, client, visitor_data, data_sync_id, http_base_url) return po_token def _get_pot_via_http(self, ydl, client, visitor_data, data_sync_id, base_url): + if base_url is None: + base_url = 'http://127.0.0.1:4416' try: response = ydl.urlopen(Request(f'{base_url}/get_pot', data=json.dumps({ 'client': client, 'visitor_data': visitor_data, - 'data_sync_id': data_sync_id + 'data_sync_id': data_sync_id, }).encode(), headers={'Content-Type': 'application/json'})) except Exception as e: raise RequestError(f'Error reaching POST /get_pot: {str(e)}') try: - response_json = json.loads(response.read().decode('utf-8')) + response_json = json.load(response) except Exception as e: - raise RequestError(f'Error parsing response JSON. response = {response.read().decode("utf-8")}', cause=e) + raise RequestError( + f'Error parsing response JSON(caused by {str(e)}). response = {response.read().decode()}') if error_msg := response_json.get('error'): raise RequestError(error_msg) @@ -57,35 +66,40 @@ def _get_pot_via_script(self, script_path, visitor_data, data_sync_id): if not os.path.isfile(script_path): raise RequestError(f"Script path doesn't exist: {script_path}") if os.path.basename(script_path) != 'generate_once.js': - raise RequestError('Incorrect script passed to extractor args. Path to generate_once.js required') + raise RequestError( + 'Incorrect script passed to extractor args. Path to generate_once.js required') if shutil.which('node') is None: raise RequestError('node is not in PATH') - # possibly vulnerable to shell injection here? but risk is low command_args = ['node', script_path] if data_sync_id: command_args.extend(['-d', data_sync_id]) elif visitor_data: command_args.extend(['-v', visitor_data]) else: - raise RequestError('Unexpected missing visitorData/dataSyncId in _get_pot_via_script') - self._logger.debug(f'Executing command to get POT via script: {" ".join(command_args)}') + raise RequestError( + 'Unexpected missing visitorData and dataSyncId in _get_pot_via_script') + self._logger.debug( + f'Executing command to get POT via script: {" ".join(command_args)}') try: stdout, stderr, returncode = Popen.run( command_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) except Exception as e: - raise RequestError('_get_pot_via_script failed: Unable to run script', cause=e) + raise RequestError( + f'_get_pot_via_script failed: Unable to run script(caused by {str(e)})') self._logger.debug(f'stdout = {stdout}') if returncode: raise RequestError( f'_get_pot_via_script failed with returncode {returncode}:\n{stderr.strip()}') - # the JSON response is always the last line + # The JSON response is always the last line script_data_resp = stdout.splitlines()[-1] - self._logger.debug(f'_get_pot_via_script response = {script_data_resp}') + self._logger.debug( + f'_get_pot_via_script response = {script_data_resp}') try: return json.loads(script_data_resp)['poToken'] except (json.JSONDecodeError, TypeError, KeyError) as e: - raise RequestError('Error parsing JSON response from _get_pot_via_script', cause=e) + raise RequestError( + f'Error parsing JSON response from _get_pot_via_script(caused by {str(e)})') From 19f84a6b58e6fb8c0402c725b0cc414b337c86d5 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 10 Sep 2024 00:07:33 -0700 Subject: [PATCH 7/9] Update README with getpot_bgutil_baseurl/port documentation --- README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 42d649b..c814279 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,6 @@ The provider comes in two parts: > This plugin is not ready for general use and is awaiting changes to be merged in yt-dlp for it to be functional. > Follow https://github.com/yt-dlp/yt-dlp/pull/10648 for updates. -Default port number is 4416. If you want to change this, be sure to change it in both the provider and plugin code. - ### Base Requirements If using Docker image for option (a) for the provider, the Docker runtime is required. @@ -96,4 +94,22 @@ This will automatically install [coletdjnz's POT plugin framework](https://githu If using option (a) HTTP Server for the provider, use yt-dlp like normal 🙂. -If using option (b) script for the provider, you need to pass the extractor argument `getpot_bgutil_script` to `youtube` for each yt-dlp call. The argument should include the path to the transpiled generation script (`server/build/generate_once.js`). E.g. `--extractor-args "youtube:getpot_bgutil_script=$WORKSPACE/bgutil-ytdlp-pot-provider/server/build/generate_once.js"` +If you want to change the port number used by the provider server, use the `--port` option. + +```shell +node build/main.js --port 8080 +``` + +If changing the port or IP used for the provider server, pass it to yt-dlp via `getpot_bgutil_baseurl` + +```shell +--extractor-args youtube:getpot_bgutil_baseurl=127.0.0.1:8080 +``` + +--- + +If using option (b) script for the provider, you need to pass the extractor argument `getpot_bgutil_script` to `youtube` for each yt-dlp call. The argument should include the path to the transpiled generation script (`server/build/generate_once.js`). + +```shell +--extractor-args "youtube:getpot_bgutil_script=$WORKSPACE/bgutil-ytdlp-pot-provider/server/build/generate_once.js" +``` From e889d47c5c7b62910b4782edaae6ed3db24e4048 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 10 Sep 2024 00:08:38 -0700 Subject: [PATCH 8/9] Add quotes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c814279..764f4f3 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ node build/main.js --port 8080 If changing the port or IP used for the provider server, pass it to yt-dlp via `getpot_bgutil_baseurl` ```shell ---extractor-args youtube:getpot_bgutil_baseurl=127.0.0.1:8080 +--extractor-args "youtube:getpot_bgutil_baseurl=127.0.0.1:8080" ``` --- From b1338b8e51a441021dcbf645a37bc0c6723e8756 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 10 Sep 2024 00:44:09 -0700 Subject: [PATCH 9/9] add http scheme to example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 764f4f3..2e1576d 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ node build/main.js --port 8080 If changing the port or IP used for the provider server, pass it to yt-dlp via `getpot_bgutil_baseurl` ```shell ---extractor-args "youtube:getpot_bgutil_baseurl=127.0.0.1:8080" +--extractor-args "youtube:getpot_bgutil_baseurl=http://127.0.0.1:8080" ``` ---