diff --git a/ytdl_subscribe/cli/subscription_args_parser.py b/ytdl_subscribe/cli/download_args_parser.py similarity index 91% rename from ytdl_subscribe/cli/subscription_args_parser.py rename to ytdl_subscribe/cli/download_args_parser.py index d821fe63..8e8d95a7 100644 --- a/ytdl_subscribe/cli/subscription_args_parser.py +++ b/ytdl_subscribe/cli/download_args_parser.py @@ -1,3 +1,4 @@ +import hashlib from argparse import ArgumentError from typing import Dict from typing import List @@ -5,7 +6,7 @@ from typing import List from mergedeep import mergedeep -class SubscriptionArgsParser: +class DownloadArgsParser: """ 'Extra' arguments can be given to `ytdl-sub dl` which are meant to represent fields in a subscription yaml. This class will convert those extra args into a dict that can be @@ -50,7 +51,6 @@ class SubscriptionArgsParser: # Remove the argument --'s, then split on period arg_name_split = arg_name.replace("--", "", 1).split(".") - next_arg_name = arg_name_split[0] next_dict = argument_dict for next_arg_name in arg_name_split[:-1]: next_dict[next_arg_name] = {} @@ -82,3 +82,10 @@ class SubscriptionArgsParser: mergedeep.merge(subscription_dict, argument_dict, strategy=mergedeep.Strategy.REPLACE) return subscription_dict + + def get_args_hash(self) -> str: + """ + :return: Hash of the arguments provided + """ + hash_string = str(sorted(self._unknown_arguments)) + return hashlib.sha256(hash_string.encode()).hexdigest()[-8:] diff --git a/ytdl_subscribe/cli/main_args_parser.py b/ytdl_subscribe/cli/main_args_parser.py new file mode 100644 index 00000000..310ddf30 --- /dev/null +++ b/ytdl_subscribe/cli/main_args_parser.py @@ -0,0 +1,29 @@ +import argparse + +################################################################################################### +# GLOBAL PARSER +parser = argparse.ArgumentParser( + description="YoutubeDL-Subscribe: Download and organize your favorite media hassle-free." +) +parser.add_argument( + "-c", + "--config", + metavar="CONFIGPATH", + type=str, + help="path to the config yaml, uses config.yaml if not provided", + default="config.yaml", +) +################################################################################################### +# SUBSCRIPTION PARSER +subparsers = parser.add_subparsers(dest="subparser") +subscription_parser = subparsers.add_parser("sub") +subscription_parser.add_argument( + "subscription_paths", + metavar="SUBPATH", + nargs="*", + help="path to subscription files, uses config.yaml if not provided", + default="config.yaml", +) +################################################################################################### +# DOWNLOAD PARSER +download_parser = subparsers.add_parser("dl") diff --git a/ytdl_subscribe/main.py b/ytdl_subscribe/main.py index 77653513..5a979410 100644 --- a/ytdl_subscribe/main.py +++ b/ytdl_subscribe/main.py @@ -1,69 +1,82 @@ import argparse import sys -from random import randint +import traceback from typing import List -################################################################################################### -# GLOBAL PARSER -from ytdl_subscribe.cli.subscription_args_parser import SubscriptionArgsParser +from ytdl_subscribe.cli.download_args_parser import DownloadArgsParser +from ytdl_subscribe.cli.main_args_parser import parser from ytdl_subscribe.validators.config.config_file_validator import ConfigFileValidator from ytdl_subscribe.validators.config.subscription_validator import SubscriptionValidator +from ytdl_subscribe.validators.exceptions import ValidationException -parser = argparse.ArgumentParser( - description="YoutubeDL-Subscribe: Download and organize your favorite media hassle-free." -) -parser.add_argument( - "-c", - "--config", - metavar="CONFIGPATH", - type=str, - help="path to the config yaml, uses config.yaml if not provided", - default="config.yaml", -) -################################################################################################### -# SUBSCRIPTION PARSER -subparsers = parser.add_subparsers(dest="subparser") -subscription_parser = subparsers.add_parser("sub") -subscription_parser.add_argument( - "subscription_paths", - metavar="SUBPATH", - nargs="*", - help="path to subscription files, uses config.yaml if not provided", - default="config.yaml", -) -################################################################################################### -# DOWNLOAD PARSER -download_parser = subparsers.add_parser("dl") -if __name__ == "__main__": +def _download_subscriptions_from_yaml_files( + config: ConfigFileValidator, args: argparse.Namespace +) -> None: + """ + Downloads all subscriptions from one or many subscription yaml files. + + :param config: Configuration file + :param args: Arguments from argparse + """ + subscription_paths: List[str] = args.subscription_paths + subscriptions: List[SubscriptionValidator] = [] + + for subscription_path in subscription_paths: + subscriptions += SubscriptionValidator.from_file_path( + config=config, subscription_path=subscription_path + ) + + for subscription in subscriptions: + subscription.to_subscription().download() + + +def _download_subscription_from_cli(config: ConfigFileValidator, extra_args: List[str]) -> None: + """ + Downloads a one-off subscription using the CLI + + :param config: Configuration file + :param extra_args: Extra arguments from argparse that contain dynamic subscription options + """ + dl_args_parser = DownloadArgsParser(extra_arguments=extra_args) + subscription_args_dict = dl_args_parser.to_subscription_dict() + + subscription_name = f"cli-dl-{dl_args_parser.get_args_hash()}" + SubscriptionValidator.from_dict( + config=config, + subscription_name=subscription_name, + subscription_dict=subscription_args_dict, + ).to_subscription().download() + + +def _main(): + """Entrypoint for ytdl-subscribe""" args, extra_args = parser.parse_known_args() config: ConfigFileValidator = ConfigFileValidator.from_file_path(args.config) if args.subparser == "sub": - subscription_paths: List[str] = args.subscription_paths - subscriptions: List[SubscriptionValidator] = [] - - for subscription_path in subscription_paths: - subscriptions += SubscriptionValidator.from_file_path( - config=config, subscription_path=subscription_path - ) - - for subscription in subscriptions: - subscription.to_subscription().download() - + _download_subscriptions_from_yaml_files(config=config, args=args) print("Subscription download complete!") # One-off download if args.subparser == "dl": - subscription_name = f"cli-dl-{randint(0, 999)}" - subscription_args_dict = SubscriptionArgsParser( - extra_arguments=extra_args - ).to_subscription_dict() - SubscriptionValidator.from_dict( - config=config, - subscription_name=subscription_name, - subscription_dict=subscription_args_dict, - ).to_subscription().download() + _download_subscription_from_cli(config=config, extra_args=extra_args) print("Download complete!") + +if __name__ == "__main__": + try: + _main() + except ValidationException as validation_exception: + print(validation_exception) + sys.exit(1) + except Exception as exc: # pylint: disable=broad-except + print(traceback.format_exc()) + print( + "A fatal error occurred. Please copy and paste the stacktrace above and make a Github " + "issue at https://github.com/jmbannon/ytdl-subscribe/issues with your config and " + "command/subscription yaml file to reproduce. Thanks for trying ytdl-subscribe!" + ) + sys.exit(1) + sys.exit(0) diff --git a/ytdl_subscribe/validators/config/subscription_validator.py b/ytdl_subscribe/validators/config/subscription_validator.py index 725ab9f9..c6d0185f 100644 --- a/ytdl_subscribe/validators/config/subscription_validator.py +++ b/ytdl_subscribe/validators/config/subscription_validator.py @@ -60,7 +60,7 @@ class SubscriptionValidator(StrictDictValidator): if preset_name not in self.config.presets.keys: raise self._validation_exception( - f"'preset '{preset_name}' does not exist in the provided config. " + f"preset '{preset_name}' does not exist in the provided config. " f"Available presets: {', '.join(self.config.presets.keys)}" )