From 91116aa6f7e26cf68449f53908e835ecb112185d Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Wed, 31 Dec 2025 19:28:22 -0800 Subject: [PATCH] [FEATURE] inspect subcommand --- src/ytdl_sub/cli/entrypoint.py | 43 +++++++++++++ src/ytdl_sub/cli/parsers/main.py | 46 ++++++++++++++ tests/integration/cli/test_inspect.py | 91 +++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 tests/integration/cli/test_inspect.py diff --git a/src/ytdl_sub/cli/entrypoint.py b/src/ytdl_sub/cli/entrypoint.py index b825d45e..26816a1f 100644 --- a/src/ytdl_sub/cli/entrypoint.py +++ b/src/ytdl_sub/cli/entrypoint.py @@ -24,6 +24,8 @@ from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_lock import working_directory_lock from ytdl_sub.utils.logger import Logger +# pylint: disable=too-many-branches + logger = Logger.get() # View is a command to run a simple dry-run on a URL using the `_view` preset. @@ -196,6 +198,32 @@ def _view_url_from_cli(config: ConfigFile, url: str, split_chapters: bool) -> Su return subscription +def _inspect( + config: ConfigFile, + subscription_paths: List[str], + subscription_matches: List[str], + subscription_override_dict: Dict, +) -> None: + + subscriptions: List[Subscription] = [] + for path in subscription_paths: + subscriptions += Subscription.from_file_path( + config=config, + subscription_path=path, + subscription_matches=subscription_matches, + subscription_override_dict=subscription_override_dict, + ) + + if len(subscriptions) > 1: + print( + "inspect can only inspect a single subscription. " + "Use --match to filter for a single one" + ) + return + + print(subscriptions[0].resolved_yaml()) + + def main() -> List[Subscription]: """ Entrypoint for ytdl-sub, without the error handling @@ -222,6 +250,21 @@ def main() -> List[Subscription]: subscriptions: List[Subscription] = [] + if args.subparser == "inspect": + subscription_override_dict = {} + if args.dl_override: + subscription_override_dict = DownloadArgsParser.from_dl_override( + override=args.dl_override, config=config + ).to_subscription_dict() + + _inspect( + config=config, + subscription_paths=args.subscription_paths, + subscription_matches=args.match, + subscription_override_dict=subscription_override_dict, + ) + return [] + # If transaction log file is specified, make sure we can open it _maybe_validate_transaction_log_file(transaction_log_file_path=args.transaction_log) diff --git a/src/ytdl_sub/cli/parsers/main.py b/src/ytdl_sub/cli/parsers/main.py index aa26b591..b1be0f67 100644 --- a/src/ytdl_sub/cli/parsers/main.py +++ b/src/ytdl_sub/cli/parsers/main.py @@ -225,3 +225,49 @@ view_parser.add_argument("url", help="URL to view source variables for") ################################################################################################### # CLI-TO-SUB PARSER cli_to_sub_parser = subparsers.add_parser("cli-to-sub") + +################################################################################################### +# INSPECT PARSER + + +class InspectArguments: + INCLUDE_OVERRIDES = CLIArgument( + short="-v", + long="--include-overrides", + ) + + +inspect_parser = subparsers.add_parser("inspect") +inspect_parser.add_argument( + InspectArguments.INCLUDE_OVERRIDES.short, + InspectArguments.INCLUDE_OVERRIDES.long, + action="store_true", + help="Whether to show override variables.", + default=False, +) +inspect_parser.add_argument( + MainArguments.MATCH.short, + MainArguments.MATCH.long, + dest="match", + nargs="+", + action="extend", + type=str, + help="match subscription names to one or more substrings, and only run those subscriptions", + default=[], +) + +inspect_parser.add_argument( + "subscription_paths", + metavar="SUBPATH", + nargs="*", + help="path to subscription files, uses subscriptions.yaml if not provided", + default=["subscriptions.yaml"], +) + +inspect_parser.add_argument( + SubArguments.OVERRIDE.short, + SubArguments.OVERRIDE.long, + type=str, + help="override all subscription config values using `dl` syntax, " + "i.e. --dl-override='--ytdl_options.max_downloads 3'", +) diff --git a/tests/integration/cli/test_inspect.py b/tests/integration/cli/test_inspect.py new file mode 100644 index 00000000..02de77e3 --- /dev/null +++ b/tests/integration/cli/test_inspect.py @@ -0,0 +1,91 @@ +from pathlib import Path + +import pytest +import yaml +from conftest import mock_run_from_cli + + +class TestInspect: + @pytest.mark.parametrize("config_provided", [True, False]) + def test_inspect_command( + self, + capsys, + default_config_path: str, + tv_show_subscriptions_path: Path, + output_directory: str, + config_provided: bool, + ): + args = f"--config {default_config_path} " if config_provided else "" + args += f"inspect {tv_show_subscriptions_path} --match 'Jake Trains'" + + subscriptions = mock_run_from_cli(args=args) + assert len(subscriptions) == 0 + + captured_as_dict = yaml.safe_load(capsys.readouterr().out) + assert captured_as_dict == { + "chapters": { + "allow_chapters_from_comments": False, + "embed_chapters": True, + "enable": "True", + "force_key_frames": False, + }, + "date_range": {"breaks": "True", "enable": "True", "type": "upload_date"}, + "download": [ + { + "download_reverse": "True", + "include_sibling_metadata": False, + "playlist_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped", + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped", + }, + ], + "source_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped", + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped", + }, + ], + "url": "https://www.youtube.com/@JakeTrains", + "variables": {}, + "webpage_url": "{modified_webpage_url}", + "ytdl_options": {}, + } + ], + "file_convert": {"convert_to": "mp4", "convert_with": "yt-dlp", "enable": "True"}, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-Jake Trains-download-archive.json", + "file_name": "{episode_file_path}.{ext}", + "info_json_name": "{episode_file_path}.{info_json_ext}", + "keep_files_date_eval": "{episode_date_standardized}", + "maintain_download_archive": True, + "output_directory": f"{output_directory}/Jake Trains", + "preserve_mtime": False, + "thumbnail_name": "{thumbnail_file_name}", + }, + "throttle_protection": { + "enable": True, + "sleep_per_download_s": {"max": "28.4", "min": "13.8"}, + "sleep_per_request_s": {"max": "0.75", "min": "0.0"}, + "sleep_per_subscription_s": {"max": "26.1", "min": "16.3"}, + }, + "video_tags": { + "contentRating": "{episode_content_rating}", + "date": "{episode_date_standardized}", + "episode_id": "{episode_number}", + "genre": "{tv_show_genre}", + "show": "{tv_show_name}", + "synopsis": "{episode_plot}", + "title": "{episode_title}", + "year": "{episode_year}", + }, + }