ytdl-sub 2026.1.30__tar.gz → 2026.2.2.post1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/PKG-INFO +1 -1
- ytdl_sub-2026.2.2.post1/src/ytdl_sub/__init__.py +1 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/entrypoint.py +9 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/main.py +11 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/preset.py +9 -2
- ytdl_sub-2026.2.2.post1/src/ytdl_sub/config/validators/variable_validation.py +196 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/variable_definitions.py +10 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/script.py +99 -27
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/function.py +0 -7
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/syntax_tree.py +4 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/variable_dependency.py +4 -1
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/base_subscription.py +15 -4
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/string_formatter_validators.py +33 -19
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/PKG-INFO +1 -1
- ytdl_sub-2026.1.30/src/ytdl_sub/__init__.py +0 -1
- ytdl_sub-2026.1.30/src/ytdl_sub/config/validators/variable_validation.py +0 -94
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/LICENSE +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/README.md +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/pyproject.toml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/setup.cfg +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/output_summary.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/output_transaction_log.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/cli_to_sub.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/dl.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/config_file.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/config_validator.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/defaults.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/overrides.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/plugin.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/plugin_mapping.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/plugin_operation.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/preset_plugins.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/preset_options.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/validators/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/validators/options.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/info_json/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/source_plugin.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/url/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/url/downloader.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/url/validators.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/ytdl_options_builder.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/ytdlp.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/base_entry.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/entry.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/entry_parent.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/custom_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/function_scripts.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/variable_types.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/variables/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/variables/override_variables.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/main.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/audio_extract.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/chapters.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/date_range.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/embed_thumbnail.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/file_convert.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/filter_exclude.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/filter_include.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/format.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/internal/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/internal/view.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/match_filters.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/music_tags.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/nfo_tags.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/output_directory_nfo_tags.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/split_by_chapters.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/square_thumbnail.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/static_nfo_tags.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/subtitles.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/throttle_protection.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/video_tags.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/media_quality.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/players.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/internal/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/internal/view.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/singles.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_base.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_extras.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_videos.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/array_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/boolean_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/conditional_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/date_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/error_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/json_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/map_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/numeric_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/print_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/regex_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/string_functions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/parser.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/script_output.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/array.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/map.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/resolvable.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/variable.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/exception_formatters.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/exceptions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/name_validation.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/type_checking.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription_download.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription_validators.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/thread/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/thread/log_entries_downloaded_listener.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/chapters.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/datetime.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/exceptions.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/ffmpeg.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/file_handler.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/file_lock.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/file_path.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/logger.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/retry.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/script.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/scriptable.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/subtitles.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/system.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/thumbnail.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/xml.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/yaml.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/audo_codec_validator.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/file_path_validators.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/nfo_validators.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/regex_validator.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/source_variable_validator.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/strict_dict_validator.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/string_datetime.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/string_select_validator.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/validators.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/ytdl_additions/__init__.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/SOURCES.txt +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/dependency_links.txt +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/entry_points.txt +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/requires.txt +0 -0
- {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pypi_version__ = "2026.02.02.post1";__local_version__ = "2026.02.02+ced547b"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import gc
|
|
2
2
|
import os
|
|
3
|
+
import random
|
|
3
4
|
import sys
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from pathlib import Path
|
|
@@ -75,6 +76,7 @@ def _download_subscriptions_from_yaml_files(
|
|
|
75
76
|
subscription_override_dict: Dict,
|
|
76
77
|
update_with_info_json: bool,
|
|
77
78
|
dry_run: bool,
|
|
79
|
+
shuffle: bool,
|
|
78
80
|
) -> List[Subscription]:
|
|
79
81
|
"""
|
|
80
82
|
Downloads all subscriptions from one or many subscription yaml files.
|
|
@@ -91,6 +93,8 @@ def _download_subscriptions_from_yaml_files(
|
|
|
91
93
|
Whether to actually download or update using existing info json
|
|
92
94
|
dry_run
|
|
93
95
|
Whether to dry run or not
|
|
96
|
+
shuffle
|
|
97
|
+
Whether to shuffle the subscription download order
|
|
94
98
|
|
|
95
99
|
Returns
|
|
96
100
|
-------
|
|
@@ -112,6 +116,10 @@ def _download_subscriptions_from_yaml_files(
|
|
|
112
116
|
subscription_override_dict=subscription_override_dict,
|
|
113
117
|
)
|
|
114
118
|
|
|
119
|
+
if shuffle:
|
|
120
|
+
logger.info("Shuffling subscriptions")
|
|
121
|
+
random.shuffle(subscriptions)
|
|
122
|
+
|
|
115
123
|
for subscription in subscriptions:
|
|
116
124
|
with subscription.exception_handling():
|
|
117
125
|
logger.info(
|
|
@@ -253,6 +261,7 @@ def main() -> List[Subscription]:
|
|
|
253
261
|
subscription_override_dict=subscription_override_dict,
|
|
254
262
|
update_with_info_json=args.update_with_info_json,
|
|
255
263
|
dry_run=args.dry_run,
|
|
264
|
+
shuffle=args.shuffle,
|
|
256
265
|
)
|
|
257
266
|
|
|
258
267
|
# One-off download
|
|
@@ -172,6 +172,10 @@ class SubArguments:
|
|
|
172
172
|
short="-o",
|
|
173
173
|
long="--dl-override",
|
|
174
174
|
)
|
|
175
|
+
SHUFFLE = CLIArgument(
|
|
176
|
+
short="-sh",
|
|
177
|
+
long="--shuffle",
|
|
178
|
+
)
|
|
175
179
|
|
|
176
180
|
|
|
177
181
|
subscription_parser = subparsers.add_parser("sub")
|
|
@@ -197,6 +201,13 @@ subscription_parser.add_argument(
|
|
|
197
201
|
help="override all subscription config values using `dl` syntax, "
|
|
198
202
|
"i.e. --dl-override='--ytdl_options.max_downloads 3'",
|
|
199
203
|
)
|
|
204
|
+
subscription_parser.add_argument(
|
|
205
|
+
SubArguments.SHUFFLE.short,
|
|
206
|
+
SubArguments.SHUFFLE.long,
|
|
207
|
+
action="store_true",
|
|
208
|
+
help="shuffle subscription order when downloading",
|
|
209
|
+
default=False,
|
|
210
|
+
)
|
|
200
211
|
|
|
201
212
|
###################################################################################################
|
|
202
213
|
# DOWNLOAD PARSER
|
|
@@ -255,11 +255,18 @@ class Preset(_PresetShell):
|
|
|
255
255
|
"""
|
|
256
256
|
return cls(config=config, name=preset_name, value=preset_dict)
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
def yaml(self) -> str:
|
|
258
|
+
def yaml(self, subscription_only: bool) -> str:
|
|
260
259
|
"""
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
subscription_only:
|
|
263
|
+
Only include the subscription contents, not the surrounding boiler-plate.
|
|
264
|
+
|
|
261
265
|
Returns
|
|
262
266
|
-------
|
|
263
267
|
Preset in YAML format
|
|
264
268
|
"""
|
|
269
|
+
if subscription_only:
|
|
270
|
+
return dump_yaml(self._value)
|
|
271
|
+
|
|
265
272
|
return dump_yaml({"presets": {self._name: self._value}})
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
from typing import List
|
|
3
|
+
from typing import Set
|
|
4
|
+
|
|
5
|
+
from ytdl_sub.config.overrides import Overrides
|
|
6
|
+
from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
|
|
7
|
+
from ytdl_sub.config.plugin.plugin_operation import PluginOperation
|
|
8
|
+
from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
|
|
9
|
+
from ytdl_sub.config.preset_options import OutputOptions
|
|
10
|
+
from ytdl_sub.config.validators.options import OptionsValidator
|
|
11
|
+
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
|
12
|
+
from ytdl_sub.entries.script.variable_definitions import UNRESOLVED_VARIABLES
|
|
13
|
+
from ytdl_sub.entries.script.variable_definitions import VARIABLES
|
|
14
|
+
from ytdl_sub.script.script import Script
|
|
15
|
+
from ytdl_sub.script.utils.name_validation import is_function
|
|
16
|
+
from ytdl_sub.utils.script import ScriptUtils
|
|
17
|
+
from ytdl_sub.validators.string_formatter_validators import validate_formatters
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ResolutionLevel:
|
|
21
|
+
ORIGINAL = 0
|
|
22
|
+
FILL = 1
|
|
23
|
+
RESOLVE = 2
|
|
24
|
+
INTERNAL = 3
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def name_of(cls, resolution_level: int) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Name of the resolution level.
|
|
30
|
+
"""
|
|
31
|
+
if resolution_level == cls.ORIGINAL:
|
|
32
|
+
return "original"
|
|
33
|
+
if resolution_level == cls.FILL:
|
|
34
|
+
return "fill"
|
|
35
|
+
if resolution_level == cls.RESOLVE:
|
|
36
|
+
return "resolve"
|
|
37
|
+
if resolution_level == cls.INTERNAL:
|
|
38
|
+
return "internal"
|
|
39
|
+
raise ValueError("Invalid resolution level")
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def all(cls) -> List[int]:
|
|
43
|
+
"""
|
|
44
|
+
All possible resolution levels.
|
|
45
|
+
"""
|
|
46
|
+
return [cls.ORIGINAL, cls.FILL, cls.RESOLVE, cls.INTERNAL]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class VariableValidation:
|
|
50
|
+
|
|
51
|
+
def _get_resolve_partial_filter(self) -> Set[str]:
|
|
52
|
+
# Exclude sanitized variables from partial validation. This lessens the work
|
|
53
|
+
# and prevents double-evaluation, which can lead to bad behavior like double-prints.
|
|
54
|
+
return {
|
|
55
|
+
name
|
|
56
|
+
for name in self.script.variable_names
|
|
57
|
+
if name not in self.unresolved_variables and not name.endswith("_sanitized")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def _apply_resolution_level(self) -> None:
|
|
61
|
+
if self._resolution_level == ResolutionLevel.FILL:
|
|
62
|
+
self.unresolved_variables |= VARIABLES.variable_names(include_sanitized=True)
|
|
63
|
+
# Only partial resolve definitions that are already resolved
|
|
64
|
+
self.unresolved_variables |= {
|
|
65
|
+
name
|
|
66
|
+
for name in self.overrides.keys
|
|
67
|
+
if not is_function(name) and not self.script.definition_of(name).maybe_resolvable
|
|
68
|
+
}
|
|
69
|
+
elif self._resolution_level == ResolutionLevel.RESOLVE:
|
|
70
|
+
# Partial resolve everything, but not including internal variables
|
|
71
|
+
self.unresolved_variables |= VARIABLES.variable_names(include_sanitized=True)
|
|
72
|
+
elif self._resolution_level == ResolutionLevel.INTERNAL:
|
|
73
|
+
# Partial resolve everything including internal variables
|
|
74
|
+
pass
|
|
75
|
+
else:
|
|
76
|
+
raise ValueError("Invalid resolution level for validation")
|
|
77
|
+
|
|
78
|
+
self.script = self.script.resolve_partial(
|
|
79
|
+
unresolvable=self.unresolved_variables,
|
|
80
|
+
output_filter=self._get_resolve_partial_filter(),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
overrides: Overrides,
|
|
86
|
+
downloader_options: MultiUrlValidator,
|
|
87
|
+
output_options: OutputOptions,
|
|
88
|
+
plugins: PresetPlugins,
|
|
89
|
+
resolution_level: int = ResolutionLevel.RESOLVE,
|
|
90
|
+
):
|
|
91
|
+
self.overrides = overrides
|
|
92
|
+
self.downloader_options = downloader_options
|
|
93
|
+
self.output_options = output_options
|
|
94
|
+
self.plugins = plugins
|
|
95
|
+
|
|
96
|
+
self.script: Script = self.overrides.script
|
|
97
|
+
self.unresolved_variables = (
|
|
98
|
+
self.plugins.get_all_variables(
|
|
99
|
+
additional_options=[self.output_options, self.downloader_options]
|
|
100
|
+
)
|
|
101
|
+
| UNRESOLVED_VARIABLES
|
|
102
|
+
)
|
|
103
|
+
self.unresolved_runtime_variables = self.plugins.get_all_variables(
|
|
104
|
+
additional_options=[self.output_options, self.downloader_options]
|
|
105
|
+
)
|
|
106
|
+
self._resolution_level = resolution_level
|
|
107
|
+
|
|
108
|
+
self._apply_resolution_level()
|
|
109
|
+
|
|
110
|
+
def _add_runtime_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
|
|
111
|
+
"""
|
|
112
|
+
Add dummy variables for script validation
|
|
113
|
+
"""
|
|
114
|
+
added_variables = options.added_variables(
|
|
115
|
+
unresolved_variables=self.unresolved_runtime_variables,
|
|
116
|
+
).get(plugin_op, set())
|
|
117
|
+
modified_variables = options.modified_variables().get(plugin_op, set())
|
|
118
|
+
|
|
119
|
+
self.unresolved_runtime_variables -= added_variables | modified_variables
|
|
120
|
+
|
|
121
|
+
def ensure_proper_usage(self, partial_resolve_formatters: bool = False) -> Dict:
|
|
122
|
+
"""
|
|
123
|
+
Validate variables resolve as plugins are executed, and return
|
|
124
|
+
a mock script which contains actualized added variables from the plugins
|
|
125
|
+
"""
|
|
126
|
+
resolved_subscription: Dict = {}
|
|
127
|
+
|
|
128
|
+
self._add_runtime_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
|
|
129
|
+
|
|
130
|
+
# Always add output options first
|
|
131
|
+
self._add_runtime_variables(
|
|
132
|
+
PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Metadata variables to be added
|
|
136
|
+
for plugin_options in PluginMapping.order_options_by(
|
|
137
|
+
self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA
|
|
138
|
+
):
|
|
139
|
+
self._add_runtime_variables(
|
|
140
|
+
PluginOperation.MODIFY_ENTRY_METADATA, options=plugin_options
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
for plugin_options in PluginMapping.order_options_by(
|
|
144
|
+
self.plugins.zipped(), PluginOperation.MODIFY_ENTRY
|
|
145
|
+
):
|
|
146
|
+
self._add_runtime_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options)
|
|
147
|
+
|
|
148
|
+
# Validate that any formatter in the plugin options can resolve
|
|
149
|
+
resolved_subscription |= validate_formatters(
|
|
150
|
+
script=self.script,
|
|
151
|
+
unresolved_variables=self.unresolved_variables,
|
|
152
|
+
unresolved_runtime_variables=self.unresolved_runtime_variables,
|
|
153
|
+
validator=plugin_options,
|
|
154
|
+
partial_resolve_formatters=partial_resolve_formatters,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
resolved_subscription |= validate_formatters(
|
|
158
|
+
script=self.script,
|
|
159
|
+
unresolved_variables=self.unresolved_variables,
|
|
160
|
+
unresolved_runtime_variables=self.unresolved_runtime_variables,
|
|
161
|
+
validator=self.output_options,
|
|
162
|
+
partial_resolve_formatters=partial_resolve_formatters,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# TODO: make this a function
|
|
166
|
+
raw_download_output = validate_formatters(
|
|
167
|
+
script=self.script,
|
|
168
|
+
unresolved_variables=self.unresolved_variables,
|
|
169
|
+
unresolved_runtime_variables=self.unresolved_runtime_variables,
|
|
170
|
+
validator=self.downloader_options.urls,
|
|
171
|
+
partial_resolve_formatters=partial_resolve_formatters,
|
|
172
|
+
)
|
|
173
|
+
resolved_subscription["download"] = []
|
|
174
|
+
for url_output in raw_download_output["download"]:
|
|
175
|
+
if isinstance(url_output["url"], list):
|
|
176
|
+
url_output["url"] = [url for url in url_output["url"] if bool(url)]
|
|
177
|
+
|
|
178
|
+
if url_output["url"]:
|
|
179
|
+
resolved_subscription["download"].append(url_output)
|
|
180
|
+
|
|
181
|
+
# TODO: make function
|
|
182
|
+
resolved_subscription["overrides"] = {}
|
|
183
|
+
for name in self.overrides.keys:
|
|
184
|
+
value = self.script.definition_of(name)
|
|
185
|
+
if name in self.script.function_names:
|
|
186
|
+
# Keep custom functions as-is
|
|
187
|
+
resolved_subscription["overrides"][name] = self.overrides.dict_with_format_strings[
|
|
188
|
+
name
|
|
189
|
+
]
|
|
190
|
+
elif resolved := value.maybe_resolvable:
|
|
191
|
+
resolved_subscription["overrides"][name] = resolved.native
|
|
192
|
+
else:
|
|
193
|
+
resolved_subscription["overrides"][name] = ScriptUtils.to_native_script(value)
|
|
194
|
+
|
|
195
|
+
assert not self.unresolved_runtime_variables
|
|
196
|
+
return resolved_subscription
|
{ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/variable_definitions.py
RENAMED
|
@@ -1135,6 +1135,16 @@ class VariableDefinitions(
|
|
|
1135
1135
|
]
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
|
+
@cache
|
|
1139
|
+
def variable_names(self, include_sanitized: bool):
|
|
1140
|
+
"""
|
|
1141
|
+
Returns all variable names, and can include sanitized.
|
|
1142
|
+
"""
|
|
1143
|
+
var_names: Set[str] = self.scripts().keys()
|
|
1144
|
+
if include_sanitized:
|
|
1145
|
+
var_names |= {f"{name}_sanitized" for name in var_names}
|
|
1146
|
+
return var_names
|
|
1147
|
+
|
|
1138
1148
|
@cache
|
|
1139
1149
|
def injected_variables(self) -> Set[MetadataVariable]:
|
|
1140
1150
|
"""
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# pylint: disable=missing-raises-doc
|
|
2
|
-
import copy
|
|
3
2
|
from collections import defaultdict
|
|
4
3
|
from typing import Dict
|
|
5
4
|
from typing import List
|
|
@@ -693,6 +692,18 @@ class Script:
|
|
|
693
692
|
|
|
694
693
|
raise RuntimeException(f"Tried to get unresolved variable {variable_name}")
|
|
695
694
|
|
|
695
|
+
def definition_of(self, name: str) -> SyntaxTree:
|
|
696
|
+
"""
|
|
697
|
+
Returns
|
|
698
|
+
-------
|
|
699
|
+
The definition of the variable or function.
|
|
700
|
+
"""
|
|
701
|
+
if name.startswith("%") and name[1:] in self._functions:
|
|
702
|
+
return self._functions[name[1:]]
|
|
703
|
+
if name in self._variables:
|
|
704
|
+
return self._variables[name]
|
|
705
|
+
raise RuntimeException(f"Tried to get non-existent definition with name {name}")
|
|
706
|
+
|
|
696
707
|
@property
|
|
697
708
|
def variable_names(self) -> Set[str]:
|
|
698
709
|
"""
|
|
@@ -713,35 +724,33 @@ class Script:
|
|
|
713
724
|
"""
|
|
714
725
|
return set(to_function_definition_name(name) for name in self._functions.keys())
|
|
715
726
|
|
|
716
|
-
def
|
|
727
|
+
def _resolve_partial_loop(
|
|
717
728
|
self,
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
unresolvable: Set[str] = unresolvable or {}
|
|
727
|
-
resolved: Dict[Variable, Resolvable] = {}
|
|
728
|
-
unresolved: Dict[Variable, Argument] = {
|
|
729
|
-
Variable(name): definition
|
|
730
|
-
for name, definition in self._variables.items()
|
|
731
|
-
if name not in unresolvable
|
|
732
|
-
}
|
|
729
|
+
output_filter: Optional[Set[str]],
|
|
730
|
+
resolved: Dict[Variable, Resolvable],
|
|
731
|
+
unresolved: Dict[Variable, Argument],
|
|
732
|
+
unresolvable: Optional[Set[str]],
|
|
733
|
+
):
|
|
734
|
+
to_partially_resolve: Set[Variable] = (
|
|
735
|
+
{Variable(name) for name in output_filter} if output_filter else set(unresolved.keys())
|
|
736
|
+
)
|
|
733
737
|
|
|
734
738
|
partially_resolved = True
|
|
735
739
|
while partially_resolved:
|
|
736
740
|
|
|
737
741
|
partially_resolved = False
|
|
738
742
|
|
|
739
|
-
for variable in list(
|
|
743
|
+
for variable in list(to_partially_resolve):
|
|
740
744
|
definition = unresolved[variable]
|
|
741
|
-
|
|
742
745
|
maybe_resolved = definition
|
|
746
|
+
|
|
743
747
|
if isinstance(definition, Variable) and definition.name not in unresolvable:
|
|
744
|
-
|
|
748
|
+
if definition in resolved:
|
|
749
|
+
maybe_resolved = resolved[definition]
|
|
750
|
+
elif definition in unresolved:
|
|
751
|
+
maybe_resolved = unresolved[definition]
|
|
752
|
+
else:
|
|
753
|
+
raise UNREACHABLE
|
|
745
754
|
elif isinstance(definition, VariableDependency):
|
|
746
755
|
maybe_resolved = definition.partial_resolve(
|
|
747
756
|
resolved_variables=resolved,
|
|
@@ -752,6 +761,7 @@ class Script:
|
|
|
752
761
|
if isinstance(maybe_resolved, Resolvable):
|
|
753
762
|
resolved[variable] = maybe_resolved
|
|
754
763
|
del unresolved[variable]
|
|
764
|
+
to_partially_resolve.remove(variable)
|
|
755
765
|
partially_resolved = True
|
|
756
766
|
else:
|
|
757
767
|
unresolved[variable] = maybe_resolved
|
|
@@ -760,11 +770,73 @@ class Script:
|
|
|
760
770
|
# which means we can iterate again
|
|
761
771
|
partially_resolved |= definition != maybe_resolved
|
|
762
772
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
773
|
+
def _resolve_partial(
|
|
774
|
+
self,
|
|
775
|
+
unresolvable: Optional[Set[str]] = None,
|
|
776
|
+
output_filter: Optional[Set[str]] = None,
|
|
777
|
+
) -> Dict[str, SyntaxTree]:
|
|
778
|
+
"""
|
|
779
|
+
Returns
|
|
780
|
+
-------
|
|
781
|
+
New (deep-copied) script that resolves inner variables as much
|
|
782
|
+
as possible.
|
|
783
|
+
"""
|
|
784
|
+
unresolvable: Set[str] = unresolvable or {}
|
|
785
|
+
resolved: Dict[Variable, Resolvable] = {}
|
|
786
|
+
unresolved: Dict[Variable, Argument] = {
|
|
787
|
+
Variable(name): definition
|
|
788
|
+
for name, definition in self._variables.items()
|
|
789
|
+
if name not in unresolvable
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
self._resolve_partial_loop(
|
|
793
|
+
output_filter=output_filter,
|
|
794
|
+
resolved=resolved,
|
|
795
|
+
unresolved=unresolved,
|
|
796
|
+
unresolvable=unresolvable,
|
|
770
797
|
)
|
|
798
|
+
|
|
799
|
+
if output_filter:
|
|
800
|
+
out: Dict[str, SyntaxTree] = {}
|
|
801
|
+
for name in output_filter:
|
|
802
|
+
variable_name = Variable(name)
|
|
803
|
+
if variable_name in resolved:
|
|
804
|
+
out[name] = ResolvedSyntaxTree(ast=[resolved[variable_name]])
|
|
805
|
+
else:
|
|
806
|
+
out[name] = SyntaxTree(ast=[unresolved[variable_name]])
|
|
807
|
+
|
|
808
|
+
return out
|
|
809
|
+
|
|
810
|
+
return {
|
|
811
|
+
var.name: ResolvedSyntaxTree(ast=[definition]) for var, definition in resolved.items()
|
|
812
|
+
} | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()}
|
|
813
|
+
|
|
814
|
+
def resolve_partial(
|
|
815
|
+
self,
|
|
816
|
+
unresolvable: Optional[Set[str]] = None,
|
|
817
|
+
output_filter: Optional[Set[str]] = None,
|
|
818
|
+
) -> "Script":
|
|
819
|
+
"""
|
|
820
|
+
Updates the internal script to resolve as much as possible.
|
|
821
|
+
"""
|
|
822
|
+
out = self._resolve_partial(unresolvable=unresolvable, output_filter=output_filter)
|
|
823
|
+
for var_name, definition in out.items():
|
|
824
|
+
self._variables[var_name] = definition
|
|
825
|
+
|
|
826
|
+
return self
|
|
827
|
+
|
|
828
|
+
def resolve_partial_once(
|
|
829
|
+
self, variable_definitions: Dict[str, SyntaxTree], unresolvable: Optional[Set[str]] = None
|
|
830
|
+
) -> Dict[str, SyntaxTree]:
|
|
831
|
+
"""
|
|
832
|
+
Partially resolves the input variable definitions as much as possible.
|
|
833
|
+
"""
|
|
834
|
+
try:
|
|
835
|
+
self.add_parsed(variable_definitions)
|
|
836
|
+
return self._resolve_partial(
|
|
837
|
+
unresolvable=unresolvable,
|
|
838
|
+
output_filter=set(list(variable_definitions.keys())),
|
|
839
|
+
)
|
|
840
|
+
finally:
|
|
841
|
+
for name in variable_definitions.keys():
|
|
842
|
+
self._variables.pop(name, None)
|
|
@@ -371,13 +371,6 @@ class BuiltInFunction(Function, BuiltInFunctionType):
|
|
|
371
371
|
If the conditional partially resolvable enough to warrant evaluation,
|
|
372
372
|
perform it here.
|
|
373
373
|
"""
|
|
374
|
-
if self.is_subset_of(
|
|
375
|
-
variables=resolved_variables, custom_function_definitions=custom_functions
|
|
376
|
-
):
|
|
377
|
-
return self.resolve(
|
|
378
|
-
resolved_variables=resolved_variables,
|
|
379
|
-
custom_functions=custom_functions,
|
|
380
|
-
)
|
|
381
374
|
|
|
382
375
|
if self.name == "if":
|
|
383
376
|
maybe_resolvable_arg, is_resolvable = VariableDependency.try_partial_resolve(
|
|
@@ -55,6 +55,10 @@ class SyntaxTree(VariableDependency):
|
|
|
55
55
|
custom_functions=custom_functions,
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
+
# If no arguments, must be empty string
|
|
59
|
+
if len(maybe_resolvable_values) == 0:
|
|
60
|
+
return String(value="")
|
|
61
|
+
|
|
58
62
|
# Mimic the above resolve behavior
|
|
59
63
|
if len(maybe_resolvable_values) > 1:
|
|
60
64
|
return BuiltInFunction(name="concat", args=maybe_resolvable_values)
|
{ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/variable_dependency.py
RENAMED
|
@@ -279,8 +279,11 @@ class VariableDependency(ABC):
|
|
|
279
279
|
if not isinstance(maybe_resolvable_args[-1], Resolvable):
|
|
280
280
|
is_resolvable = False
|
|
281
281
|
elif isinstance(arg, Variable):
|
|
282
|
-
if arg
|
|
282
|
+
if arg in resolved_variables:
|
|
283
|
+
maybe_resolvable_args[-1] = resolved_variables[arg]
|
|
284
|
+
else:
|
|
283
285
|
is_resolvable = False
|
|
286
|
+
# Could be un unresolvable
|
|
284
287
|
if arg in unresolved_variables:
|
|
285
288
|
maybe_resolvable_args[-1] = unresolved_variables[arg]
|
|
286
289
|
|
{ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/base_subscription.py
RENAMED
|
@@ -8,6 +8,7 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
|
|
|
8
8
|
from ytdl_sub.config.preset import Preset
|
|
9
9
|
from ytdl_sub.config.preset_options import OutputOptions
|
|
10
10
|
from ytdl_sub.config.preset_options import YTDLOptions
|
|
11
|
+
from ytdl_sub.config.validators.variable_validation import ResolutionLevel
|
|
11
12
|
from ytdl_sub.config.validators.variable_validation import VariableValidation
|
|
12
13
|
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
|
13
14
|
from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
|
|
@@ -79,7 +80,7 @@ class BaseSubscription(ABC):
|
|
|
79
80
|
)
|
|
80
81
|
|
|
81
82
|
# Validate after adding the subscription name
|
|
82
|
-
|
|
83
|
+
_ = VariableValidation(
|
|
83
84
|
overrides=self.overrides,
|
|
84
85
|
downloader_options=self.downloader_options,
|
|
85
86
|
output_options=self.output_options,
|
|
@@ -254,12 +255,22 @@ class BaseSubscription(ABC):
|
|
|
254
255
|
-------
|
|
255
256
|
Subscription in yaml format
|
|
256
257
|
"""
|
|
257
|
-
return self._preset_options.yaml
|
|
258
|
+
return self._preset_options.yaml(subscription_only=False)
|
|
258
259
|
|
|
259
|
-
def resolved_yaml(self) -> str:
|
|
260
|
+
def resolved_yaml(self, resolution_level: int = ResolutionLevel.RESOLVE) -> str:
|
|
260
261
|
"""
|
|
261
262
|
Returns
|
|
262
263
|
-------
|
|
263
264
|
Human-readable, condensed YAML definition of the subscription.
|
|
264
265
|
"""
|
|
265
|
-
|
|
266
|
+
if resolution_level == ResolutionLevel.ORIGINAL:
|
|
267
|
+
return self._preset_options.yaml(subscription_only=True)
|
|
268
|
+
|
|
269
|
+
out = VariableValidation(
|
|
270
|
+
overrides=self.overrides,
|
|
271
|
+
downloader_options=self.downloader_options,
|
|
272
|
+
output_options=self.output_options,
|
|
273
|
+
plugins=self.plugins,
|
|
274
|
+
resolution_level=resolution_level,
|
|
275
|
+
).ensure_proper_usage(partial_resolve_formatters=True)
|
|
276
|
+
return dump_yaml(out)
|