ytdl-sub 2025.12.30__tar.gz → 2025.12.31__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.
Files changed (173) hide show
  1. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/PKG-INFO +1 -1
  2. ytdl_sub-2025.12.31/src/ytdl_sub/__init__.py +1 -0
  3. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/overrides.py +20 -13
  4. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/plugin/preset_plugins.py +33 -0
  5. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/preset.py +35 -9
  6. ytdl_sub-2025.12.31/src/ytdl_sub/config/validators/variable_validation.py +91 -0
  7. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/url/validators.py +2 -2
  8. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/parser.py +5 -0
  9. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/script.py +93 -28
  10. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/utils/name_validation.py +21 -0
  11. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/subscriptions/base_subscription.py +21 -3
  12. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/script.py +19 -2
  13. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/string_formatter_validators.py +63 -31
  14. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/validators.py +3 -13
  15. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub.egg-info/PKG-INFO +1 -1
  16. ytdl_sub-2025.12.30/src/ytdl_sub/__init__.py +0 -1
  17. ytdl_sub-2025.12.30/src/ytdl_sub/config/validators/variable_validation.py +0 -215
  18. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/LICENSE +0 -0
  19. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/README.md +0 -0
  20. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/pyproject.toml +0 -0
  21. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/setup.cfg +0 -0
  22. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/__init__.py +0 -0
  23. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/entrypoint.py +0 -0
  24. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/output_summary.py +0 -0
  25. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/output_transaction_log.py +0 -0
  26. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/parsers/__init__.py +0 -0
  27. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/parsers/cli_to_sub.py +0 -0
  28. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/parsers/dl.py +0 -0
  29. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/cli/parsers/main.py +0 -0
  30. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/__init__.py +0 -0
  31. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/config_file.py +0 -0
  32. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/config_validator.py +0 -0
  33. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/defaults.py +0 -0
  34. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/plugin/__init__.py +0 -0
  35. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/plugin/plugin.py +0 -0
  36. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/plugin/plugin_mapping.py +0 -0
  37. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/plugin/plugin_operation.py +0 -0
  38. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/preset_options.py +0 -0
  39. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/validators/__init__.py +0 -0
  40. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/config/validators/options.py +0 -0
  41. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/__init__.py +0 -0
  42. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/info_json/__init__.py +0 -0
  43. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +0 -0
  44. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/source_plugin.py +0 -0
  45. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/url/__init__.py +0 -0
  46. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/url/downloader.py +0 -0
  47. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/ytdl_options_builder.py +0 -0
  48. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/downloaders/ytdlp.py +0 -0
  49. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/__init__.py +0 -0
  50. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/base_entry.py +0 -0
  51. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/entry.py +0 -0
  52. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/entry_parent.py +0 -0
  53. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/script/__init__.py +0 -0
  54. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/script/custom_functions.py +0 -0
  55. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/script/function_scripts.py +0 -0
  56. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/script/variable_definitions.py +0 -0
  57. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/script/variable_types.py +0 -0
  58. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/variables/__init__.py +0 -0
  59. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/entries/variables/override_variables.py +0 -0
  60. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/main.py +0 -0
  61. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/__init__.py +0 -0
  62. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/audio_extract.py +0 -0
  63. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/chapters.py +0 -0
  64. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/date_range.py +0 -0
  65. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/embed_thumbnail.py +0 -0
  66. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/file_convert.py +0 -0
  67. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/filter_exclude.py +0 -0
  68. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/filter_include.py +0 -0
  69. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/format.py +0 -0
  70. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/internal/__init__.py +0 -0
  71. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/internal/view.py +0 -0
  72. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/match_filters.py +0 -0
  73. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/music_tags.py +0 -0
  74. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/nfo_tags.py +0 -0
  75. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/output_directory_nfo_tags.py +0 -0
  76. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/split_by_chapters.py +0 -0
  77. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/square_thumbnail.py +0 -0
  78. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/static_nfo_tags.py +0 -0
  79. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/subtitles.py +0 -0
  80. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/throttle_protection.py +0 -0
  81. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/plugins/video_tags.py +0 -0
  82. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/__init__.py +0 -0
  83. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/__init__.py +0 -0
  84. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +0 -0
  85. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml +0 -0
  86. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml +0 -0
  87. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/media_quality.yaml +0 -0
  88. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/players.yaml +0 -0
  89. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +0 -0
  90. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +0 -0
  91. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml +0 -0
  92. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +0 -0
  93. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/internal/__init__.py +0 -0
  94. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/internal/view.yaml +0 -0
  95. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music/__init__.py +0 -0
  96. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +0 -0
  97. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +0 -0
  98. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music/singles.yaml +0 -0
  99. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +0 -0
  100. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music_videos/__init__.py +0 -0
  101. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_base.yaml +0 -0
  102. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_extras.yaml +0 -0
  103. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/music_videos/music_videos.yaml +0 -0
  104. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +0 -0
  105. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +0 -0
  106. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +0 -0
  107. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +0 -0
  108. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +0 -0
  109. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/__init__.py +0 -0
  110. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/__init__.py +0 -0
  111. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/array_functions.py +0 -0
  112. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/boolean_functions.py +0 -0
  113. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/conditional_functions.py +0 -0
  114. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/date_functions.py +0 -0
  115. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/error_functions.py +0 -0
  116. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/json_functions.py +0 -0
  117. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/map_functions.py +0 -0
  118. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/numeric_functions.py +0 -0
  119. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/print_functions.py +0 -0
  120. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/regex_functions.py +0 -0
  121. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/functions/string_functions.py +0 -0
  122. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/script_output.py +0 -0
  123. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/__init__.py +0 -0
  124. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/array.py +0 -0
  125. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/function.py +0 -0
  126. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/map.py +0 -0
  127. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/resolvable.py +0 -0
  128. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/syntax_tree.py +0 -0
  129. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/variable.py +0 -0
  130. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/types/variable_dependency.py +0 -0
  131. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/utils/__init__.py +0 -0
  132. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/utils/exception_formatters.py +0 -0
  133. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/utils/exceptions.py +0 -0
  134. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/script/utils/type_checking.py +0 -0
  135. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/subscriptions/__init__.py +0 -0
  136. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/subscriptions/subscription.py +0 -0
  137. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/subscriptions/subscription_download.py +0 -0
  138. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/subscriptions/subscription_validators.py +0 -0
  139. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +0 -0
  140. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/thread/__init__.py +0 -0
  141. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/thread/log_entries_downloaded_listener.py +0 -0
  142. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/__init__.py +0 -0
  143. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/chapters.py +0 -0
  144. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/datetime.py +0 -0
  145. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/exceptions.py +0 -0
  146. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/ffmpeg.py +0 -0
  147. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/file_handler.py +0 -0
  148. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/file_lock.py +0 -0
  149. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/file_path.py +0 -0
  150. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/logger.py +0 -0
  151. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/retry.py +0 -0
  152. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/scriptable.py +0 -0
  153. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/subtitles.py +0 -0
  154. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/system.py +0 -0
  155. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/thumbnail.py +0 -0
  156. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/xml.py +0 -0
  157. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/utils/yaml.py +0 -0
  158. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/__init__.py +0 -0
  159. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/audo_codec_validator.py +0 -0
  160. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/file_path_validators.py +0 -0
  161. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/nfo_validators.py +0 -0
  162. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/regex_validator.py +0 -0
  163. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/source_variable_validator.py +0 -0
  164. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/strict_dict_validator.py +0 -0
  165. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/string_datetime.py +0 -0
  166. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/validators/string_select_validator.py +0 -0
  167. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/ytdl_additions/__init__.py +0 -0
  168. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +0 -0
  169. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub.egg-info/SOURCES.txt +0 -0
  170. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub.egg-info/dependency_links.txt +0 -0
  171. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub.egg-info/entry_points.txt +0 -0
  172. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/src/ytdl_sub.egg-info/requires.txt +0 -0
  173. {ytdl_sub-2025.12.30 → ytdl_sub-2025.12.31}/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: 2025.12.30
3
+ Version: 2025.12.31
4
4
  Summary: Automate downloading metadata generation with YoutubeDL
5
5
  Author: Jesse Bannon
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -0,0 +1 @@
1
+ __pypi_version__ = "2025.12.31";__local_version__ = "2025.12.31+1abe2a4"
@@ -4,15 +4,16 @@ from typing import Iterable
4
4
  from typing import Optional
5
5
  from typing import Set
6
6
 
7
- import mergedeep
8
-
9
7
  from ytdl_sub.entries.entry import Entry
10
8
  from ytdl_sub.entries.script.variable_definitions import VARIABLES
11
9
  from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
12
10
  from ytdl_sub.entries.variables.override_variables import OverrideHelpers
13
11
  from ytdl_sub.script.parser import parse
14
12
  from ytdl_sub.script.script import Script
13
+ from ytdl_sub.script.types.function import BuiltInFunction
15
14
  from ytdl_sub.script.types.resolvable import Resolvable
15
+ from ytdl_sub.script.types.resolvable import String
16
+ from ytdl_sub.script.types.syntax_tree import SyntaxTree
16
17
  from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
17
18
  from ytdl_sub.utils.exceptions import InvalidVariableNameException
18
19
  from ytdl_sub.utils.exceptions import StringFormattingException
@@ -134,29 +135,35 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
134
135
  )
135
136
 
136
137
  def initial_variables(
137
- self, unresolved_variables: Optional[Dict[str, str]] = None
138
- ) -> Dict[str, str]:
138
+ self, unresolved_variables: Optional[Dict[str, SyntaxTree]] = None
139
+ ) -> Dict[str, SyntaxTree]:
139
140
  """
140
141
  Returns
141
142
  -------
142
143
  Variables and format strings for all Override variables + additional variables (Optional)
143
144
  """
144
- initial_variables: Dict[str, str] = {}
145
- mergedeep.merge(
146
- initial_variables,
147
- self.dict_with_format_strings,
148
- unresolved_variables if unresolved_variables else {},
149
- )
150
- return ScriptUtils.add_sanitized_variables(initial_variables)
145
+ initial_variables: Dict[str, SyntaxTree] = self.dict_with_parsed_format_strings
146
+ if unresolved_variables:
147
+ initial_variables |= unresolved_variables
148
+ return ScriptUtils.add_sanitized_parsed_variables(initial_variables)
151
149
 
152
150
  def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides":
153
151
  """
154
152
  Initialize the override script with any unresolved variables
155
153
  """
156
- self.script.add(
154
+ self.script.add_parsed(
157
155
  self.initial_variables(
158
156
  unresolved_variables={
159
- var_name: f"{{%throw('Plugin variable {var_name} has not been created yet')}}"
157
+ var_name: SyntaxTree(
158
+ ast=[
159
+ BuiltInFunction(
160
+ name="throw",
161
+ args=[
162
+ String(f"Plugin variable {var_name} has not been created yet")
163
+ ],
164
+ )
165
+ ]
166
+ )
160
167
  for var_name in unresolved_variables
161
168
  }
162
169
  )
@@ -1,5 +1,7 @@
1
+ from typing import Iterable
1
2
  from typing import List
2
3
  from typing import Optional
4
+ from typing import Set
3
5
  from typing import Tuple
4
6
  from typing import Type
5
7
 
@@ -44,3 +46,34 @@ class PresetPlugins:
44
46
  if plugin_type in plugin_option_types:
45
47
  return self.plugin_options[plugin_option_types.index(plugin_type)]
46
48
  return None
49
+
50
+ def get_added_and_modified_variables(
51
+ self, additional_options: List[OptionsValidator]
52
+ ) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
53
+ """
54
+ Iterates and returns the plugin options, added variables, modified variables
55
+ """
56
+ for plugin_options in self.plugin_options + additional_options:
57
+ added_variables: Set[str] = set()
58
+ modified_variables: Set[str] = set()
59
+
60
+ for plugin_added_variables in plugin_options.added_variables(
61
+ unresolved_variables=set(),
62
+ ).values():
63
+ added_variables |= set(plugin_added_variables)
64
+
65
+ for plugin_modified_variables in plugin_options.modified_variables().values():
66
+ modified_variables = plugin_modified_variables
67
+
68
+ yield plugin_options, added_variables, modified_variables
69
+
70
+ def get_all_variables(self, additional_options: List[OptionsValidator]) -> Set[str]:
71
+ """
72
+ Returns set of all added and modified variables' names.
73
+ """
74
+ all_variables: Set[str] = set()
75
+ for _, added, modified in self.get_added_and_modified_variables(additional_options):
76
+ all_variables.update(added)
77
+ all_variables.update(modified)
78
+
79
+ return all_variables
@@ -2,6 +2,7 @@ import copy
2
2
  from typing import Any
3
3
  from typing import Dict
4
4
  from typing import List
5
+ from typing import Set
5
6
 
6
7
  from mergedeep import mergedeep
7
8
 
@@ -11,7 +12,6 @@ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
11
12
  from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
12
13
  from ytdl_sub.config.preset_options import OutputOptions
13
14
  from ytdl_sub.config.preset_options import YTDLOptions
14
- from ytdl_sub.config.validators.variable_validation import VariableValidation
15
15
  from ytdl_sub.downloaders.url.validators import MultiUrlValidator
16
16
  from ytdl_sub.prebuilt_presets import PREBUILT_PRESET_NAMES
17
17
  from ytdl_sub.prebuilt_presets import PUBLISHED_PRESET_NAMES
@@ -172,6 +172,37 @@ class Preset(_PresetShell):
172
172
  mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE)
173
173
  )
174
174
 
175
+ def _initialize_overrides_script(self, overrides: Overrides) -> Overrides:
176
+ """
177
+ Do some gymnastics to initialize the Overrides script.
178
+ """
179
+ unresolved_variables: Set[str] = set()
180
+
181
+ for (
182
+ plugin_options,
183
+ added_variables,
184
+ modified_variables,
185
+ ) in self.plugins.get_added_and_modified_variables(
186
+ additional_options=[self.downloader_options, self.output_options]
187
+ ):
188
+ for added_variable in added_variables:
189
+ if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
190
+ # pylint: disable=protected-access
191
+ raise plugin_options._validation_exception(
192
+ f"Cannot use the variable name {added_variable} because it exists as a"
193
+ " built-in ytdl-sub variable name."
194
+ )
195
+ # pylint: enable=protected-access
196
+
197
+ # Set unresolved as variables that are added but do not exist as
198
+ # entry/override variables since they are created at run-time
199
+ unresolved_variables |= added_variables | modified_variables
200
+
201
+ # Initialize overrides with unresolved variables + modified variables to throw an error.
202
+ # For modified variables, this is to prevent a resolve(update=True) to setting any
203
+ # dependencies until it has been explicitly added
204
+ return overrides.initialize_script(unresolved_variables=unresolved_variables)
205
+
175
206
  def __init__(self, config: ConfigValidator, name: str, value: Any):
176
207
  super().__init__(name=name, value=value)
177
208
 
@@ -192,16 +223,11 @@ class Preset(_PresetShell):
192
223
  )
193
224
 
194
225
  self.plugins: PresetPlugins = self._validate_and_get_plugins()
195
- self.overrides = self._validate_key(key="overrides", validator=Overrides, default={})
196
-
226
+ self.overrides = self._initialize_overrides_script(
227
+ overrides=self._validate_key(key="overrides", validator=Overrides, default={})
228
+ )
197
229
  self.overrides.ensure_variable_names_not_a_plugin(plugin_names=PRESET_KEYS)
198
230
 
199
- VariableValidation(
200
- downloader_options=self.downloader_options,
201
- output_options=self.output_options,
202
- plugins=self.plugins,
203
- ).initialize_preset_overrides(overrides=self.overrides).ensure_proper_usage()
204
-
205
231
  @property
206
232
  def name(self) -> str:
207
233
  """
@@ -0,0 +1,91 @@
1
+ from typing import Dict
2
+
3
+ from ytdl_sub.config.overrides import Overrides
4
+ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
5
+ from ytdl_sub.config.plugin.plugin_operation import PluginOperation
6
+ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
7
+ from ytdl_sub.config.preset_options import OutputOptions
8
+ from ytdl_sub.config.validators.options import OptionsValidator
9
+ from ytdl_sub.downloaders.url.validators import MultiUrlValidator
10
+ from ytdl_sub.validators.string_formatter_validators import validate_formatters
11
+
12
+
13
+ class VariableValidation:
14
+ def __init__(
15
+ self,
16
+ overrides: Overrides,
17
+ downloader_options: MultiUrlValidator,
18
+ output_options: OutputOptions,
19
+ plugins: PresetPlugins,
20
+ ):
21
+ self.overrides = overrides
22
+ self.downloader_options = downloader_options
23
+ self.output_options = output_options
24
+ self.plugins = plugins
25
+
26
+ self.script = self.overrides.script
27
+ self.unresolved_variables = self.plugins.get_all_variables(
28
+ additional_options=[self.output_options, self.downloader_options]
29
+ )
30
+
31
+ def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
32
+ """
33
+ Add dummy variables for script validation
34
+ """
35
+ added_variables = options.added_variables(
36
+ unresolved_variables=self.unresolved_variables,
37
+ ).get(plugin_op, set())
38
+ modified_variables = options.modified_variables().get(plugin_op, set())
39
+
40
+ self.unresolved_variables -= added_variables | modified_variables
41
+
42
+ def ensure_proper_usage(self) -> Dict:
43
+ """
44
+ Validate variables resolve as plugins are executed, and return
45
+ a mock script which contains actualized added variables from the plugins
46
+ """
47
+
48
+ resolved_subscription: Dict = {}
49
+
50
+ self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
51
+
52
+ # Always add output options first
53
+ self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options)
54
+
55
+ # Metadata variables to be added
56
+ for plugin_options in PluginMapping.order_options_by(
57
+ self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA
58
+ ):
59
+ self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=plugin_options)
60
+
61
+ for plugin_options in PluginMapping.order_options_by(
62
+ self.plugins.zipped(), PluginOperation.MODIFY_ENTRY
63
+ ):
64
+ self._add_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options)
65
+
66
+ # Validate that any formatter in the plugin options can resolve
67
+ resolved_subscription |= validate_formatters(
68
+ script=self.script,
69
+ unresolved_variables=self.unresolved_variables,
70
+ validator=plugin_options,
71
+ )
72
+
73
+ resolved_subscription |= validate_formatters(
74
+ script=self.script,
75
+ unresolved_variables=self.unresolved_variables,
76
+ validator=self.output_options,
77
+ )
78
+
79
+ # TODO: make this a function
80
+ raw_download_output = validate_formatters(
81
+ script=self.script,
82
+ unresolved_variables=self.unresolved_variables,
83
+ validator=self.downloader_options.urls,
84
+ )
85
+ resolved_subscription["download"] = []
86
+ for url_output in raw_download_output["download"]:
87
+ if url_output["url"]:
88
+ resolved_subscription["download"].append(url_output)
89
+
90
+ assert not self.unresolved_variables
91
+ return resolved_subscription
@@ -21,7 +21,7 @@ class UrlThumbnailValidator(StrictDictValidator):
21
21
  def __init__(self, name, value):
22
22
  super().__init__(name, value)
23
23
 
24
- self._name = self._validate_key(key="name", validator=StringFormatterValidator)
24
+ self._thumb_name = self._validate_key(key="name", validator=StringFormatterValidator)
25
25
  self._uid = self._validate_key(key="uid", validator=OverridesStringFormatterValidator)
26
26
 
27
27
  @property
@@ -29,7 +29,7 @@ class UrlThumbnailValidator(StrictDictValidator):
29
29
  """
30
30
  File name for the thumbnail
31
31
  """
32
- return self._name
32
+ return self._thumb_name
33
33
 
34
34
  @property
35
35
  def uid(self) -> OverridesStringFormatterValidator:
@@ -31,6 +31,8 @@ from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
31
31
  from ytdl_sub.script.utils.exceptions import InvalidVariableName
32
32
  from ytdl_sub.script.utils.exceptions import UserException
33
33
  from ytdl_sub.script.utils.exceptions import VariableDoesNotExist
34
+ from ytdl_sub.script.utils.name_validation import is_function
35
+ from ytdl_sub.script.utils.name_validation import to_function_name
34
36
  from ytdl_sub.script.utils.name_validation import validate_variable_name
35
37
 
36
38
  # pylint: disable=invalid-name
@@ -144,6 +146,9 @@ class _Parser:
144
146
  ):
145
147
  self._text = text
146
148
  self._name = name
149
+ if name and is_function(name):
150
+ self._name = to_function_name(name)
151
+
147
152
  self._custom_function_names = custom_function_names
148
153
  self._variable_names = variable_names
149
154
  self._pos = 0
@@ -20,28 +20,13 @@ from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments
20
20
  from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArguments
21
21
  from ytdl_sub.script.utils.exceptions import RuntimeException
22
22
  from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
23
+ from ytdl_sub.script.utils.name_validation import is_function
24
+ from ytdl_sub.script.utils.name_validation import to_function_definition_name
25
+ from ytdl_sub.script.utils.name_validation import to_function_name
23
26
  from ytdl_sub.script.utils.name_validation import validate_variable_name
24
27
  from ytdl_sub.script.utils.type_checking import FunctionSpec
25
28
 
26
29
 
27
- def _is_function(override_name: str):
28
- return override_name.startswith("%")
29
-
30
-
31
- def _function_name(function_key: str) -> str:
32
- """
33
- Drop the % in %custom_function
34
- """
35
- return function_key[1:]
36
-
37
-
38
- def _to_function_definition_name(function_key: str) -> str:
39
- """
40
- Add % in %custom_function
41
- """
42
- return f"%{function_key}"
43
-
44
-
45
30
  class Script:
46
31
  """
47
32
  Takes a dictionary of both
@@ -241,23 +226,23 @@ class Script:
241
226
 
242
227
  def __init__(self, script: Dict[str, str]):
243
228
  function_names: Set[str] = {
244
- _function_name(name) for name in script.keys() if _is_function(name)
229
+ to_function_name(name) for name in script.keys() if is_function(name)
245
230
  }
246
231
  variable_names: Set[str] = {
247
- validate_variable_name(name) for name in script.keys() if not _is_function(name)
232
+ validate_variable_name(name) for name in script.keys() if not is_function(name)
248
233
  }
249
234
 
250
235
  self._functions: Dict[str, SyntaxTree] = {
251
236
  # custom_function_name must be passed to properly type custom function
252
237
  # arguments uniquely if they're nested (i.e. $0 to $custom_func___0)
253
- _function_name(function_key): parse(
238
+ to_function_name(function_key): parse(
254
239
  text=function_value,
255
- name=_function_name(function_key),
240
+ name=to_function_name(function_key),
256
241
  custom_function_names=function_names,
257
242
  variable_names=variable_names,
258
243
  )
259
244
  for function_key, function_value in script.items()
260
- if _is_function(function_key)
245
+ if is_function(function_key)
261
246
  }
262
247
 
263
248
  self._variables: Dict[str, SyntaxTree] = {
@@ -268,7 +253,7 @@ class Script:
268
253
  variable_names=variable_names,
269
254
  )
270
255
  for variable_key, variable_value in script.items()
271
- if not _is_function(variable_key)
256
+ if not is_function(variable_key)
272
257
  }
273
258
  self._validate()
274
259
 
@@ -485,12 +470,12 @@ class Script:
485
470
  added_variables_to_validate: Set[str] = set()
486
471
 
487
472
  functions_to_add = {
488
- _function_name(name): definition
473
+ to_function_name(name): definition
489
474
  for name, definition in variables.items()
490
- if _is_function(name)
475
+ if is_function(name)
491
476
  }
492
477
  variables_to_add = {
493
- name: definition for name, definition in variables.items() if not _is_function(name)
478
+ name: definition for name, definition in variables.items() if not is_function(name)
494
479
  }
495
480
 
496
481
  custom_function_names = set(self._functions.keys()) | functions_to_add.keys()
@@ -520,6 +505,46 @@ class Script:
520
505
 
521
506
  return self
522
507
 
508
+ def add_parsed(self, variables: Dict[str, SyntaxTree]) -> "Script":
509
+ """
510
+ Adds already parsed, new variables to the script.
511
+
512
+ Parameters
513
+ ----------
514
+ variables
515
+ Mapping containing variable name to definition.
516
+
517
+ Returns
518
+ -------
519
+ Script
520
+ self
521
+ """
522
+ added_variables_to_validate: Set[str] = set()
523
+
524
+ functions_to_add = {
525
+ to_function_name(name): definition
526
+ for name, definition in variables.items()
527
+ if is_function(name)
528
+ }
529
+ variables_to_add = {
530
+ name: definition for name, definition in variables.items() if not is_function(name)
531
+ }
532
+
533
+ for definitions in [functions_to_add, variables_to_add]:
534
+ for name, parsed in definitions.items():
535
+ if parsed.maybe_resolvable is None:
536
+ added_variables_to_validate.add(name)
537
+
538
+ if name in functions_to_add:
539
+ self._functions[name] = parsed
540
+ else:
541
+ self._variables[name] = parsed
542
+
543
+ if added_variables_to_validate:
544
+ self._validate(added_variables=added_variables_to_validate)
545
+
546
+ return self
547
+
523
548
  def resolve_once(
524
549
  self,
525
550
  variable_definitions: Dict[str, str],
@@ -560,6 +585,46 @@ class Script:
560
585
  for name in variable_definitions.keys():
561
586
  self._variables.pop(name, None)
562
587
 
588
+ def resolve_once_parsed(
589
+ self,
590
+ variable_definitions: Dict[str, SyntaxTree],
591
+ resolved: Optional[Dict[str, Resolvable]] = None,
592
+ unresolvable: Optional[Set[str]] = None,
593
+ update: bool = False,
594
+ ) -> Dict[str, Resolvable]:
595
+ """
596
+ Given a new set of variable definitions, resolve them using the Script, but do not
597
+ add them to the Script itself.
598
+
599
+ Parameters
600
+ ----------
601
+ variable_definitions
602
+ Variables to resolve, but not store in the Script
603
+ resolved
604
+ Optional. Pre-resolved variables that should be used instead of what is in the script.
605
+ unresolvable
606
+ Optional. Unresolvable variables that will be ignored in resolution, including all
607
+ variables with a dependency to them.
608
+ update
609
+ Whether to update the script's state with resolved variables. Defaults to False.
610
+
611
+ Returns
612
+ -------
613
+ Dict[str, Resolvable]
614
+ Dict containing the variable names to their resolved values.
615
+ """
616
+ try:
617
+ self.add_parsed(variable_definitions)
618
+ return self._resolve(
619
+ pre_resolved=resolved,
620
+ unresolvable=unresolvable,
621
+ output_filter=set(list(variable_definitions.keys())),
622
+ update=update,
623
+ ).output
624
+ finally:
625
+ for name in variable_definitions.keys():
626
+ self._variables.pop(name, None)
627
+
563
628
  def get(self, variable_name: str) -> Resolvable:
564
629
  """
565
630
  Parameters
@@ -605,4 +670,4 @@ class Script:
605
670
  Set[str]
606
671
  Names of all functions within the Script.
607
672
  """
608
- return set(_to_function_definition_name(name) for name in self._functions.keys())
673
+ return set(to_function_definition_name(name) for name in self._functions.keys())
@@ -58,3 +58,24 @@ def validate_custom_function_name(custom_function_name: str) -> None:
58
58
  f"Custom function name '%{custom_function_name}' is invalid:"
59
59
  " The name is used by a built-in function and cannot be overwritten."
60
60
  )
61
+
62
+
63
+ def is_function(override_name: str):
64
+ """
65
+ Whether the definition is a function or not.
66
+ """
67
+ return override_name.startswith("%")
68
+
69
+
70
+ def to_function_name(function_key: str) -> str:
71
+ """
72
+ Drop the % in %custom_function
73
+ """
74
+ return function_key[1:]
75
+
76
+
77
+ def to_function_definition_name(function_key: str) -> str:
78
+ """
79
+ Add % in %custom_function
80
+ """
81
+ return f"%{function_key}"
@@ -8,12 +8,14 @@ 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 VariableValidation
11
12
  from ytdl_sub.downloaders.url.validators import MultiUrlValidator
12
13
  from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
13
14
  from ytdl_sub.utils.exceptions import SubscriptionPermissionError
14
15
  from ytdl_sub.utils.file_handler import FileHandler
15
16
  from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
16
17
  from ytdl_sub.utils.logger import Logger
18
+ from ytdl_sub.utils.yaml import dump_yaml
17
19
  from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
18
20
 
19
21
  logger = Logger.get("subscription")
@@ -76,6 +78,14 @@ class BaseSubscription(ABC):
76
78
  }
77
79
  )
78
80
 
81
+ # Validate after adding the subscription name
82
+ self._validated_dict = VariableValidation(
83
+ overrides=self.overrides,
84
+ downloader_options=self.downloader_options,
85
+ output_options=self.output_options,
86
+ plugins=self.plugins,
87
+ ).ensure_proper_usage()
88
+
79
89
  self._enhanced_download_archive: Optional[EnhancedDownloadArchive] = (
80
90
  _initialize_download_archive(
81
91
  output_options=self.output_options,
@@ -88,9 +98,9 @@ class BaseSubscription(ABC):
88
98
  # Add post-archive variables
89
99
  self.overrides.add(
90
100
  {
91
- SubscriptionVariables.subscription_has_download_archive(): f"""{{
92
- %bool({self.download_archive.num_entries > 0})
93
- }}""",
101
+ SubscriptionVariables.subscription_has_download_archive(): (
102
+ f"{{%bool({self.download_archive.num_entries > 0})}}"
103
+ ),
94
104
  }
95
105
  )
96
106
 
@@ -245,3 +255,11 @@ class BaseSubscription(ABC):
245
255
  Subscription in yaml format
246
256
  """
247
257
  return self._preset_options.yaml
258
+
259
+ def resolved_yaml(self) -> str:
260
+ """
261
+ Returns
262
+ -------
263
+ Human-readable, condensed YAML definition of the subscription.
264
+ """
265
+ return dump_yaml(self._validated_dict)
@@ -4,7 +4,6 @@ from typing import Any
4
4
  from typing import Dict
5
5
 
6
6
  from ytdl_sub.script.parser import parse
7
- from ytdl_sub.script.script import _is_function
8
7
  from ytdl_sub.script.types.array import UnresolvedArray
9
8
  from ytdl_sub.script.types.function import BuiltInFunction
10
9
  from ytdl_sub.script.types.function import Function
@@ -15,8 +14,10 @@ from ytdl_sub.script.types.resolvable import Float
15
14
  from ytdl_sub.script.types.resolvable import Integer
16
15
  from ytdl_sub.script.types.resolvable import Lambda
17
16
  from ytdl_sub.script.types.resolvable import String
17
+ from ytdl_sub.script.types.syntax_tree import SyntaxTree
18
18
  from ytdl_sub.script.types.variable import Variable
19
19
  from ytdl_sub.script.utils.exceptions import UNREACHABLE
20
+ from ytdl_sub.script.utils.name_validation import is_function
20
21
 
21
22
  # pylint: disable=too-many-return-statements
22
23
 
@@ -30,10 +31,26 @@ class ScriptUtils:
30
31
  sanitized_variables = {
31
32
  f"{name}_sanitized": f"{{%sanitize({name})}}"
32
33
  for name in variables.keys()
33
- if not _is_function(name)
34
+ if not is_function(name)
34
35
  }
35
36
  return dict(variables, **sanitized_variables)
36
37
 
38
+ @classmethod
39
+ def add_sanitized_parsed_variables(
40
+ cls, variables: Dict[str, SyntaxTree]
41
+ ) -> Dict[str, SyntaxTree]:
42
+ """
43
+ Helper to add sanitized variables to a Script
44
+ """
45
+ sanitized_variables = {
46
+ f"{name}_sanitized": SyntaxTree(
47
+ ast=[BuiltInFunction(name="sanitize", args=[Variable(name)])]
48
+ )
49
+ for name in variables.keys()
50
+ if not is_function(name)
51
+ }
52
+ return variables | sanitized_variables
53
+
37
54
  @classmethod
38
55
  def to_script(cls, value: Any, sort_keys: bool = True) -> str:
39
56
  """