ytdl-sub 2025.12.31.post1__tar.gz → 2026.2.3__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-2025.12.31.post1 → ytdl_sub-2026.2.3}/PKG-INFO +3 -3
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/pyproject.toml +2 -2
- ytdl_sub-2026.2.3/src/ytdl_sub/__init__.py +1 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/entrypoint.py +9 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/main.py +11 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/overrides.py +17 -33
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/plugin.py +1 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/preset.py +9 -2
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/preset_options.py +1 -1
- ytdl_sub-2026.2.3/src/ytdl_sub/config/validators/variable_validation.py +196 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/url/downloader.py +21 -15
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/url/validators.py +15 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/variable_definitions.py +10 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/variable_types.py +1 -9
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/date_range.py +1 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/embed_thumbnail.py +1 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/filter_exclude.py +8 -3
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/filter_include.py +7 -5
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/nfo_tags.py +1 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/square_thumbnail.py +1 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/throttle_protection.py +3 -3
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/video_tags.py +1 -1
- ytdl_sub-2026.2.3/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +177 -0
- ytdl_sub-2026.2.3/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +111 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +1 -1
- ytdl_sub-2026.2.3/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +1327 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/numeric_functions.py +17 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/script.py +186 -17
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/array.py +22 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/function.py +171 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/map.py +30 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/resolvable.py +8 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/syntax_tree.py +34 -1
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/variable_dependency.py +97 -15
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/base_subscription.py +15 -4
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription_download.py +2 -2
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +2 -2
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/script.py +56 -6
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/string_formatter_validators.py +80 -45
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/PKG-INFO +3 -3
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/requires.txt +2 -2
- ytdl_sub-2025.12.31.post1/src/ytdl_sub/__init__.py +0 -1
- ytdl_sub-2025.12.31.post1/src/ytdl_sub/config/validators/variable_validation.py +0 -91
- ytdl_sub-2025.12.31.post1/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +0 -1049
- ytdl_sub-2025.12.31.post1/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +0 -210
- ytdl_sub-2025.12.31.post1/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +0 -5693
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/LICENSE +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/README.md +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/setup.cfg +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/output_summary.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/output_transaction_log.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/cli_to_sub.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/dl.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/config_file.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/config_validator.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/defaults.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/plugin_mapping.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/plugin_operation.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/preset_plugins.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/validators/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/validators/options.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/info_json/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/source_plugin.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/url/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/ytdl_options_builder.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/ytdlp.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/base_entry.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/entry.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/entry_parent.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/custom_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/function_scripts.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/variables/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/variables/override_variables.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/main.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/audio_extract.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/chapters.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/file_convert.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/format.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/internal/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/internal/view.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/match_filters.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/music_tags.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/output_directory_nfo_tags.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/split_by_chapters.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/static_nfo_tags.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/subtitles.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/media_quality.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/players.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/internal/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/internal/view.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/singles.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_base.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_extras.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/music_videos.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/array_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/boolean_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/conditional_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/date_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/error_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/json_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/map_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/print_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/regex_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/string_functions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/parser.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/script_output.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/variable.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/exception_formatters.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/exceptions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/name_validation.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/type_checking.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription_validators.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/thread/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/thread/log_entries_downloaded_listener.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/chapters.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/datetime.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/exceptions.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/ffmpeg.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/file_handler.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/file_lock.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/file_path.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/logger.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/retry.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/scriptable.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/subtitles.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/system.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/thumbnail.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/xml.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/yaml.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/audo_codec_validator.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/file_path_validators.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/nfo_validators.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/regex_validator.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/source_variable_validator.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/strict_dict_validator.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/string_datetime.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/string_select_validator.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/validators.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/ytdl_additions/__init__.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/SOURCES.txt +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/dependency_links.txt +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/entry_points.txt +0 -0
- {ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ytdl-sub
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2026.2.3
|
|
4
4
|
Summary: Automate downloading metadata generation with YoutubeDL
|
|
5
5
|
Author: Jesse Bannon
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -689,7 +689,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
689
689
|
Requires-Python: >=3.10
|
|
690
690
|
Description-Content-Type: text/markdown
|
|
691
691
|
License-File: LICENSE
|
|
692
|
-
Requires-Dist: yt-dlp[default]==
|
|
692
|
+
Requires-Dist: yt-dlp[default]==2026.1.31
|
|
693
693
|
Requires-Dist: colorama~=0.4
|
|
694
694
|
Requires-Dist: mergedeep~=1.3
|
|
695
695
|
Requires-Dist: mediafile~=0.12
|
|
@@ -703,7 +703,7 @@ Requires-Dist: black==24.10.0; extra == "lint"
|
|
|
703
703
|
Requires-Dist: isort==7.0.0; extra == "lint"
|
|
704
704
|
Requires-Dist: pylint==4.0.1; extra == "lint"
|
|
705
705
|
Provides-Extra: docs
|
|
706
|
-
Requires-Dist: sphinx<
|
|
706
|
+
Requires-Dist: sphinx<10,>=7; extra == "docs"
|
|
707
707
|
Requires-Dist: sphinx-rtd-theme<4,>=2; extra == "docs"
|
|
708
708
|
Requires-Dist: sphinx-book-theme~=1.0; extra == "docs"
|
|
709
709
|
Requires-Dist: sphinx-copybutton~=0.5; extra == "docs"
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.11",
|
|
16
16
|
]
|
|
17
17
|
dependencies = [
|
|
18
|
-
"yt-dlp[default]==
|
|
18
|
+
"yt-dlp[default]==2026.1.31",
|
|
19
19
|
"colorama~=0.4",
|
|
20
20
|
"mergedeep~=1.3",
|
|
21
21
|
"mediafile~=0.12",
|
|
@@ -52,7 +52,7 @@ lint = [
|
|
|
52
52
|
"pylint==4.0.1",
|
|
53
53
|
]
|
|
54
54
|
docs = [
|
|
55
|
-
"sphinx>=7,<
|
|
55
|
+
"sphinx>=7,<10",
|
|
56
56
|
"sphinx-rtd-theme>=2,<4",
|
|
57
57
|
"sphinx-book-theme~=1.0",
|
|
58
58
|
"sphinx-copybutton~=0.5",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pypi_version__ = "2026.02.03";__local_version__ = "2026.02.03+d526545"
|
|
@@ -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
|
|
@@ -3,6 +3,8 @@ from typing import Dict
|
|
|
3
3
|
from typing import Iterable
|
|
4
4
|
from typing import Optional
|
|
5
5
|
from typing import Set
|
|
6
|
+
from typing import Type
|
|
7
|
+
from typing import TypeVar
|
|
6
8
|
|
|
7
9
|
from ytdl_sub.entries.entry import Entry
|
|
8
10
|
from ytdl_sub.entries.script.variable_definitions import VARIABLES
|
|
@@ -20,10 +22,11 @@ from ytdl_sub.utils.exceptions import StringFormattingException
|
|
|
20
22
|
from ytdl_sub.utils.exceptions import ValidationException
|
|
21
23
|
from ytdl_sub.utils.script import ScriptUtils
|
|
22
24
|
from ytdl_sub.utils.scriptable import Scriptable
|
|
23
|
-
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
|
|
24
25
|
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
|
|
25
26
|
from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator
|
|
26
27
|
|
|
28
|
+
ExpectedT = TypeVar("ExpectedT")
|
|
29
|
+
|
|
27
30
|
|
|
28
31
|
class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
29
32
|
"""
|
|
@@ -207,7 +210,8 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
|
207
210
|
formatter: StringFormatterValidator,
|
|
208
211
|
entry: Optional[Entry] = None,
|
|
209
212
|
function_overrides: Optional[Dict[str, str]] = None,
|
|
210
|
-
|
|
213
|
+
expected_type: Type[ExpectedT] = str,
|
|
214
|
+
) -> ExpectedT:
|
|
211
215
|
"""
|
|
212
216
|
Parameters
|
|
213
217
|
----------
|
|
@@ -217,6 +221,8 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
|
217
221
|
Optional. Entry to add source variables to the formatter
|
|
218
222
|
function_overrides
|
|
219
223
|
Optional. Explicit values to override the overrides themselves and source variables
|
|
224
|
+
expected_type
|
|
225
|
+
The expected type that should return. Defaults to string.
|
|
220
226
|
|
|
221
227
|
Returns
|
|
222
228
|
-------
|
|
@@ -227,37 +233,15 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
|
227
233
|
StringFormattingException
|
|
228
234
|
If the formatter that is trying to be resolved cannot
|
|
229
235
|
"""
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
)
|
|
236
|
+
out = formatter.post_process(
|
|
237
|
+
self._apply_to_resolvable(
|
|
238
|
+
formatter=formatter, entry=entry, function_overrides=function_overrides
|
|
239
|
+
).native
|
|
236
240
|
)
|
|
237
241
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
"""
|
|
243
|
-
Parameters
|
|
244
|
-
----------
|
|
245
|
-
formatter
|
|
246
|
-
Overrides formatter to apply
|
|
247
|
-
|
|
248
|
-
Returns
|
|
249
|
-
-------
|
|
250
|
-
The native python form of the resolved variable
|
|
251
|
-
"""
|
|
252
|
-
return self._apply_to_resolvable(
|
|
253
|
-
formatter=formatter, entry=None, function_overrides=None
|
|
254
|
-
).native
|
|
242
|
+
if not isinstance(out, expected_type):
|
|
243
|
+
raise StringFormattingException(
|
|
244
|
+
f"Expected type {expected_type.__name__}, but received '{out.__class__.__name__}'"
|
|
245
|
+
)
|
|
255
246
|
|
|
256
|
-
|
|
257
|
-
self, formatter: StringFormatterValidator, entry: Optional[Entry] = None
|
|
258
|
-
) -> bool:
|
|
259
|
-
"""
|
|
260
|
-
Apply a formatter, and evaluate it to a boolean
|
|
261
|
-
"""
|
|
262
|
-
output = self.apply_formatter(formatter=formatter, entry=entry)
|
|
263
|
-
return ScriptUtils.bool_formatter_output(output)
|
|
247
|
+
return out
|
|
@@ -48,7 +48,7 @@ class Plugin(BasePlugin[OptionsValidatorT], Generic[OptionsValidatorT], ABC):
|
|
|
48
48
|
Returns True if enabled, False if disabled.
|
|
49
49
|
"""
|
|
50
50
|
if isinstance(self.plugin_options, ToggleableOptionsDictValidator):
|
|
51
|
-
return self.overrides.
|
|
51
|
+
return self.overrides.apply_formatter(self.plugin_options.enable, expected_type=bool)
|
|
52
52
|
return True
|
|
53
53
|
|
|
54
54
|
def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]:
|
|
@@ -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}})
|
|
@@ -63,7 +63,7 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator):
|
|
|
63
63
|
native python.
|
|
64
64
|
"""
|
|
65
65
|
out = {
|
|
66
|
-
key: overrides.
|
|
66
|
+
key: overrides.apply_formatter(val, expected_type=object)
|
|
67
67
|
for key, val in self.dict.items()
|
|
68
68
|
}
|
|
69
69
|
if "cookiefile" in out:
|
|
@@ -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
|
|
@@ -52,12 +52,12 @@ class UrlDownloaderBasePluginExtension(SourcePluginExtension[MultiUrlValidator])
|
|
|
52
52
|
|
|
53
53
|
if 0 <= input_url_idx < len(self.plugin_options.urls.list):
|
|
54
54
|
validator = self.plugin_options.urls.list[input_url_idx]
|
|
55
|
-
if self.overrides.apply_formatter(validator.url)
|
|
55
|
+
if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list):
|
|
56
56
|
return validator
|
|
57
57
|
|
|
58
58
|
# Match the first validator based on the URL, if one exists
|
|
59
59
|
for validator in self.plugin_options.urls.list:
|
|
60
|
-
if self.overrides.apply_formatter(validator.url)
|
|
60
|
+
if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list):
|
|
61
61
|
return validator
|
|
62
62
|
|
|
63
63
|
# Return the first validator if none exist
|
|
@@ -382,7 +382,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|
|
382
382
|
entries_to_iter: List[Optional[Entry]] = entries
|
|
383
383
|
|
|
384
384
|
indices = list(range(len(entries_to_iter)))
|
|
385
|
-
if self.overrides.
|
|
385
|
+
if self.overrides.apply_formatter(validator.download_reverse, expected_type=bool):
|
|
386
386
|
indices = reversed(indices)
|
|
387
387
|
|
|
388
388
|
for idx in indices:
|
|
@@ -461,8 +461,8 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|
|
461
461
|
ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides)
|
|
462
462
|
)
|
|
463
463
|
|
|
464
|
-
include_sibling_metadata = self.overrides.
|
|
465
|
-
validator.include_sibling_metadata
|
|
464
|
+
include_sibling_metadata = self.overrides.apply_formatter(
|
|
465
|
+
validator.include_sibling_metadata, expected_type=bool
|
|
466
466
|
)
|
|
467
467
|
|
|
468
468
|
parents, orphan_entries = self._download_url_metadata(
|
|
@@ -487,19 +487,25 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|
|
487
487
|
# download the bottom-most urls first since they are top-priority
|
|
488
488
|
for idx, url_validator in reversed(list(enumerate(self.collection.urls.list))):
|
|
489
489
|
# URLs can be empty. If they are, then skip
|
|
490
|
-
if not (
|
|
490
|
+
if not (urls := self.overrides.apply_formatter(url_validator.url, expected_type=list)):
|
|
491
491
|
continue
|
|
492
492
|
|
|
493
|
-
for
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
493
|
+
for url in reversed(urls):
|
|
494
|
+
assert isinstance(url, str)
|
|
495
|
+
|
|
496
|
+
if not url:
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
for entry in self._download_metadata(url=url, validator=url_validator):
|
|
500
|
+
entry.initialize_script(self.overrides).add(
|
|
501
|
+
{
|
|
502
|
+
v.ytdl_sub_input_url: url,
|
|
503
|
+
v.ytdl_sub_input_url_index: idx,
|
|
504
|
+
v.ytdl_sub_input_url_count: len(self.collection.urls.list),
|
|
505
|
+
}
|
|
506
|
+
)
|
|
501
507
|
|
|
502
|
-
|
|
508
|
+
yield entry
|
|
503
509
|
|
|
504
510
|
def download(self, entry: Entry) -> Optional[Entry]:
|
|
505
511
|
"""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
from typing import Dict
|
|
3
|
+
from typing import List
|
|
3
4
|
from typing import Optional
|
|
4
5
|
from typing import Set
|
|
5
6
|
|
|
@@ -43,6 +44,19 @@ class UrlThumbnailListValidator(ListValidator[UrlThumbnailValidator]):
|
|
|
43
44
|
_inner_list_type = UrlThumbnailValidator
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
class OverridesOneOrManyUrlValidator(OverridesStringFormatterValidator):
|
|
48
|
+
def post_process(self, resolved: Any) -> List[str]:
|
|
49
|
+
if isinstance(resolved, str):
|
|
50
|
+
return [resolved]
|
|
51
|
+
if isinstance(resolved, list):
|
|
52
|
+
for value in resolved:
|
|
53
|
+
if not isinstance(value, str):
|
|
54
|
+
raise self._validation_exception("Must be a string or an array of strings.")
|
|
55
|
+
return resolved
|
|
56
|
+
|
|
57
|
+
raise self._validation_exception("Must be a string or an array of strings.")
|
|
58
|
+
|
|
59
|
+
|
|
46
60
|
class UrlValidator(StrictDictValidator):
|
|
47
61
|
_required_keys = {"url"}
|
|
48
62
|
_optional_keys = {
|
|
@@ -68,7 +82,7 @@ class UrlValidator(StrictDictValidator):
|
|
|
68
82
|
super().__init__(name, value)
|
|
69
83
|
|
|
70
84
|
# TODO: url validate using yt-dlp IE
|
|
71
|
-
self._url = self._validate_key(key="url", validator=
|
|
85
|
+
self._url = self._validate_key(key="url", validator=OverridesOneOrManyUrlValidator)
|
|
72
86
|
self._variables = self._validate_key_if_present(
|
|
73
87
|
key="variables", validator=DictFormatterValidator, default={}
|
|
74
88
|
)
|
{ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/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
|
"""
|
{ytdl_sub-2025.12.31.post1 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/variable_types.py
RENAMED
|
@@ -22,7 +22,6 @@ VariableT = TypeVar("VariableT", bound="Variable")
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def _get(
|
|
25
|
-
cast: str,
|
|
26
25
|
metadata_variable_name: str,
|
|
27
26
|
metadata_key: str,
|
|
28
27
|
variable_name: Optional[str],
|
|
@@ -47,7 +46,7 @@ def _get(
|
|
|
47
46
|
return as_type(
|
|
48
47
|
variable_name=variable_name or metadata_key,
|
|
49
48
|
metadata_key=metadata_key,
|
|
50
|
-
definition=f"{{
|
|
49
|
+
definition=f"{{ {out} }}",
|
|
51
50
|
)
|
|
52
51
|
|
|
53
52
|
|
|
@@ -182,7 +181,6 @@ class MapMetadataVariable(MetadataVariable, MapVariable):
|
|
|
182
181
|
Creates a map variable from entry metadata
|
|
183
182
|
"""
|
|
184
183
|
return _get(
|
|
185
|
-
"map",
|
|
186
184
|
metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME,
|
|
187
185
|
metadata_key=metadata_key,
|
|
188
186
|
variable_name=variable_name,
|
|
@@ -204,7 +202,6 @@ class ArrayMetadataVariable(MetadataVariable, ArrayVariable):
|
|
|
204
202
|
Creates an array variable from entry metadata
|
|
205
203
|
"""
|
|
206
204
|
return _get(
|
|
207
|
-
"array",
|
|
208
205
|
metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME,
|
|
209
206
|
metadata_key=metadata_key,
|
|
210
207
|
variable_name=variable_name,
|
|
@@ -226,7 +223,6 @@ class StringMetadataVariable(MetadataVariable, StringVariable):
|
|
|
226
223
|
Creates a string variable from entry metadata
|
|
227
224
|
"""
|
|
228
225
|
return _get(
|
|
229
|
-
"string",
|
|
230
226
|
metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME,
|
|
231
227
|
metadata_key=metadata_key,
|
|
232
228
|
variable_name=variable_name,
|
|
@@ -245,7 +241,6 @@ class StringMetadataVariable(MetadataVariable, StringVariable):
|
|
|
245
241
|
Creates a string variable from playlist metadata
|
|
246
242
|
"""
|
|
247
243
|
return _get(
|
|
248
|
-
"string",
|
|
249
244
|
metadata_variable_name=PLAYLIST_METADATA_VARIABLE_NAME,
|
|
250
245
|
metadata_key=metadata_key,
|
|
251
246
|
variable_name=variable_name,
|
|
@@ -264,7 +259,6 @@ class StringMetadataVariable(MetadataVariable, StringVariable):
|
|
|
264
259
|
Creates a string variable from source metadata
|
|
265
260
|
"""
|
|
266
261
|
return _get(
|
|
267
|
-
"string",
|
|
268
262
|
metadata_variable_name=SOURCE_METADATA_VARIABLE_NAME,
|
|
269
263
|
metadata_key=metadata_key,
|
|
270
264
|
variable_name=variable_name,
|
|
@@ -301,7 +295,6 @@ class IntegerMetadataVariable(MetadataVariable, IntegerVariable):
|
|
|
301
295
|
Creates an int variable from entry metadata
|
|
302
296
|
"""
|
|
303
297
|
return _get(
|
|
304
|
-
"int",
|
|
305
298
|
metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME,
|
|
306
299
|
metadata_key=metadata_key,
|
|
307
300
|
variable_name=variable_name,
|
|
@@ -320,7 +313,6 @@ class IntegerMetadataVariable(MetadataVariable, IntegerVariable):
|
|
|
320
313
|
Creates an int variable from playlist metadata
|
|
321
314
|
"""
|
|
322
315
|
return _get(
|
|
323
|
-
"int",
|
|
324
316
|
metadata_variable_name=PLAYLIST_METADATA_VARIABLE_NAME,
|
|
325
317
|
metadata_key=metadata_key,
|
|
326
318
|
variable_name=variable_name,
|
|
@@ -116,7 +116,7 @@ class DateRangePlugin(Plugin[DateRangeOptions]):
|
|
|
116
116
|
date_validator=self.plugin_options.after, overrides=self.overrides
|
|
117
117
|
)
|
|
118
118
|
after_filter = f"{date_type} >= {after_str}"
|
|
119
|
-
if self.overrides.
|
|
119
|
+
if self.overrides.apply_formatter(self.plugin_options.breaks, expected_type=bool):
|
|
120
120
|
breaking_match_filters.append(after_filter)
|
|
121
121
|
else:
|
|
122
122
|
match_filters.append(after_filter)
|
|
@@ -33,7 +33,7 @@ class EmbedThumbnailPlugin(Plugin[EmbedThumbnailOptions]):
|
|
|
33
33
|
|
|
34
34
|
@property
|
|
35
35
|
def _embed_thumbnail(self) -> bool:
|
|
36
|
-
return self.overrides.
|
|
36
|
+
return self.overrides.apply_formatter(self.plugin_options, expected_type=bool)
|
|
37
37
|
|
|
38
38
|
@classmethod
|
|
39
39
|
def _embed_video_thumbnail(cls, entry: Entry) -> None:
|