ytdl-sub 2026.1.11__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.
Files changed (173) hide show
  1. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/PKG-INFO +2 -2
  2. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/pyproject.toml +1 -1
  3. ytdl_sub-2026.2.3/src/ytdl_sub/__init__.py +1 -0
  4. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/entrypoint.py +9 -0
  5. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/main.py +11 -0
  6. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/overrides.py +16 -37
  7. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/plugin.py +1 -1
  8. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/preset.py +9 -2
  9. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/preset_options.py +1 -1
  10. ytdl_sub-2026.2.3/src/ytdl_sub/config/validators/variable_validation.py +196 -0
  11. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/url/downloader.py +6 -8
  12. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/url/validators.py +2 -1
  13. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/variable_definitions.py +10 -0
  14. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/date_range.py +1 -1
  15. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/embed_thumbnail.py +1 -1
  16. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/filter_exclude.py +8 -3
  17. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/filter_include.py +7 -5
  18. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/nfo_tags.py +1 -1
  19. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/square_thumbnail.py +1 -1
  20. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/throttle_protection.py +3 -3
  21. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/video_tags.py +1 -1
  22. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +1 -1
  23. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/script.py +168 -23
  24. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/array.py +22 -1
  25. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/function.py +171 -1
  26. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/map.py +30 -1
  27. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/syntax_tree.py +34 -1
  28. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/variable_dependency.py +69 -3
  29. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/base_subscription.py +15 -4
  30. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription_download.py +2 -2
  31. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +2 -2
  32. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/script.py +39 -5
  33. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/string_formatter_validators.py +74 -53
  34. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/PKG-INFO +2 -2
  35. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/requires.txt +1 -1
  36. ytdl_sub-2026.1.11/src/ytdl_sub/__init__.py +0 -1
  37. ytdl_sub-2026.1.11/src/ytdl_sub/config/validators/variable_validation.py +0 -94
  38. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/LICENSE +0 -0
  39. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/README.md +0 -0
  40. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/setup.cfg +0 -0
  41. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/__init__.py +0 -0
  42. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/output_summary.py +0 -0
  43. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/output_transaction_log.py +0 -0
  44. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/__init__.py +0 -0
  45. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/cli_to_sub.py +0 -0
  46. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/cli/parsers/dl.py +0 -0
  47. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/__init__.py +0 -0
  48. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/config_file.py +0 -0
  49. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/config_validator.py +0 -0
  50. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/defaults.py +0 -0
  51. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/__init__.py +0 -0
  52. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/plugin_mapping.py +0 -0
  53. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/plugin_operation.py +0 -0
  54. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/plugin/preset_plugins.py +0 -0
  55. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/validators/__init__.py +0 -0
  56. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/config/validators/options.py +0 -0
  57. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/__init__.py +0 -0
  58. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/info_json/__init__.py +0 -0
  59. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +0 -0
  60. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/source_plugin.py +0 -0
  61. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/url/__init__.py +0 -0
  62. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/ytdl_options_builder.py +0 -0
  63. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/downloaders/ytdlp.py +0 -0
  64. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/__init__.py +0 -0
  65. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/base_entry.py +0 -0
  66. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/entry.py +0 -0
  67. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/entry_parent.py +0 -0
  68. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/__init__.py +0 -0
  69. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/custom_functions.py +0 -0
  70. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/function_scripts.py +0 -0
  71. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/script/variable_types.py +0 -0
  72. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/variables/__init__.py +0 -0
  73. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/entries/variables/override_variables.py +0 -0
  74. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/main.py +0 -0
  75. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/__init__.py +0 -0
  76. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/audio_extract.py +0 -0
  77. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/chapters.py +0 -0
  78. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/file_convert.py +0 -0
  79. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/format.py +0 -0
  80. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/internal/__init__.py +0 -0
  81. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/internal/view.py +0 -0
  82. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/match_filters.py +0 -0
  83. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/music_tags.py +0 -0
  84. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/output_directory_nfo_tags.py +0 -0
  85. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/split_by_chapters.py +0 -0
  86. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/static_nfo_tags.py +0 -0
  87. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/plugins/subtitles.py +0 -0
  88. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/__init__.py +0 -0
  89. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/__init__.py +0 -0
  90. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +0 -0
  91. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml +0 -0
  92. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml +0 -0
  93. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/media_quality.yaml +0 -0
  94. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/players.yaml +0 -0
  95. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +0 -0
  96. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +0 -0
  97. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml +0 -0
  98. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +0 -0
  99. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/internal/__init__.py +0 -0
  100. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/internal/view.yaml +0 -0
  101. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/__init__.py +0 -0
  102. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +0 -0
  103. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +0 -0
  104. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music/singles.yaml +0 -0
  105. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/__init__.py +0 -0
  106. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_base.yaml +0 -0
  107. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_extras.yaml +0 -0
  108. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/music_videos/music_videos.yaml +0 -0
  109. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +0 -0
  110. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +0 -0
  111. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +0 -0
  112. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +0 -0
  113. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +0 -0
  114. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/__init__.py +0 -0
  115. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/__init__.py +0 -0
  116. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/array_functions.py +0 -0
  117. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/boolean_functions.py +0 -0
  118. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/conditional_functions.py +0 -0
  119. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/date_functions.py +0 -0
  120. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/error_functions.py +0 -0
  121. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/json_functions.py +0 -0
  122. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/map_functions.py +0 -0
  123. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/numeric_functions.py +0 -0
  124. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/print_functions.py +0 -0
  125. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/regex_functions.py +0 -0
  126. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/functions/string_functions.py +0 -0
  127. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/parser.py +0 -0
  128. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/script_output.py +0 -0
  129. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/__init__.py +0 -0
  130. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/resolvable.py +0 -0
  131. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/types/variable.py +0 -0
  132. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/__init__.py +0 -0
  133. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/exception_formatters.py +0 -0
  134. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/exceptions.py +0 -0
  135. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/name_validation.py +0 -0
  136. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/script/utils/type_checking.py +0 -0
  137. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/__init__.py +0 -0
  138. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription.py +0 -0
  139. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/subscriptions/subscription_validators.py +0 -0
  140. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/thread/__init__.py +0 -0
  141. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/thread/log_entries_downloaded_listener.py +0 -0
  142. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/__init__.py +0 -0
  143. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/chapters.py +0 -0
  144. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/datetime.py +0 -0
  145. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/exceptions.py +0 -0
  146. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/ffmpeg.py +0 -0
  147. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/file_handler.py +0 -0
  148. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/file_lock.py +0 -0
  149. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/file_path.py +0 -0
  150. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/logger.py +0 -0
  151. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/retry.py +0 -0
  152. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/scriptable.py +0 -0
  153. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/subtitles.py +0 -0
  154. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/system.py +0 -0
  155. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/thumbnail.py +0 -0
  156. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/xml.py +0 -0
  157. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/utils/yaml.py +0 -0
  158. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/__init__.py +0 -0
  159. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/audo_codec_validator.py +0 -0
  160. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/file_path_validators.py +0 -0
  161. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/nfo_validators.py +0 -0
  162. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/regex_validator.py +0 -0
  163. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/source_variable_validator.py +0 -0
  164. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/strict_dict_validator.py +0 -0
  165. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/string_datetime.py +0 -0
  166. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/string_select_validator.py +0 -0
  167. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/validators/validators.py +0 -0
  168. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/ytdl_additions/__init__.py +0 -0
  169. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +0 -0
  170. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/SOURCES.txt +0 -0
  171. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/dependency_links.txt +0 -0
  172. {ytdl_sub-2026.1.11 → ytdl_sub-2026.2.3}/src/ytdl_sub.egg-info/entry_points.txt +0 -0
  173. {ytdl_sub-2026.1.11 → 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: 2026.1.11
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]==2025.12.8
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
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3.11",
16
16
  ]
17
17
  dependencies = [
18
- "yt-dlp[default]==2025.12.8",
18
+ "yt-dlp[default]==2026.1.31",
19
19
  "colorama~=0.4",
20
20
  "mergedeep~=1.3",
21
21
  "mediafile~=0.12",
@@ -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
- ) -> str:
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,42 +233,15 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
227
233
  StringFormattingException
228
234
  If the formatter that is trying to be resolved cannot
229
235
  """
230
- return formatter.post_process(
231
- str(
232
- self._apply_to_resolvable(
233
- formatter=formatter, entry=entry, function_overrides=function_overrides
234
- )
235
- )
236
- )
237
-
238
- def apply_overrides_formatter_to_native(
239
- self,
240
- formatter: OverridesStringFormatterValidator,
241
- function_overrides: Optional[Dict[str, str]] = None,
242
- ) -> Any:
243
- """
244
- Parameters
245
- ----------
246
- formatter
247
- Overrides formatter to apply
248
- function_overrides
249
- Optional. Explicit values to override the overrides themselves and source variables
250
-
251
- Returns
252
- -------
253
- The native python form of the resolved variable
254
- """
255
- return formatter.post_process_native(
236
+ out = formatter.post_process(
256
237
  self._apply_to_resolvable(
257
- formatter=formatter, entry=None, function_overrides=function_overrides
238
+ formatter=formatter, entry=entry, function_overrides=function_overrides
258
239
  ).native
259
240
  )
260
241
 
261
- def evaluate_boolean(
262
- self, formatter: StringFormatterValidator, entry: Optional[Entry] = None
263
- ) -> bool:
264
- """
265
- Apply a formatter, and evaluate it to a boolean
266
- """
267
- output = self.apply_formatter(formatter=formatter, entry=entry)
268
- return ScriptUtils.bool_formatter_output(output)
242
+ if not isinstance(out, expected_type):
243
+ raise StringFormattingException(
244
+ f"Expected type {expected_type.__name__}, but received '{out.__class__.__name__}'"
245
+ )
246
+
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.evaluate_boolean(self.plugin_options.enable)
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
- @property
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.apply_overrides_formatter_to_native(val)
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 entry_input_url in self.overrides.apply_overrides_formatter_to_native(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 entry_input_url in self.overrides.apply_overrides_formatter_to_native(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.evaluate_boolean(validator.download_reverse):
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.evaluate_boolean(
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,11 +487,9 @@ 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 (urls := self.overrides.apply_overrides_formatter_to_native(url_validator.url)):
490
+ if not (urls := self.overrides.apply_formatter(url_validator.url, expected_type=list)):
491
491
  continue
492
492
 
493
- assert isinstance(urls, list)
494
-
495
493
  for url in reversed(urls):
496
494
  assert isinstance(url, str)
497
495
 
@@ -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
 
@@ -44,7 +45,7 @@ class UrlThumbnailListValidator(ListValidator[UrlThumbnailValidator]):
44
45
 
45
46
 
46
47
  class OverridesOneOrManyUrlValidator(OverridesStringFormatterValidator):
47
- def post_process_native(self, resolved: Any) -> Any:
48
+ def post_process(self, resolved: Any) -> List[str]:
48
49
  if isinstance(resolved, str):
49
50
  return [resolved]
50
51
  if isinstance(resolved, list):
@@ -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
  """
@@ -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.evaluate_boolean(self.plugin_options.breaks):
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.evaluate_boolean(self.plugin_options)
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:
@@ -7,13 +7,14 @@ from ytdl_sub.config.validators.options import OptionsValidator
7
7
  from ytdl_sub.entries.entry import Entry
8
8
  from ytdl_sub.utils.exceptions import StringFormattingException
9
9
  from ytdl_sub.utils.logger import Logger
10
- from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator
10
+ from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator
11
+ from ytdl_sub.validators.validators import ListValidator
11
12
  from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
12
13
 
13
14
  logger = Logger.get("filter-exclude")
14
15
 
15
16
 
16
- class FilterExcludeOptions(ListFormatterValidator, OptionsValidator):
17
+ class FilterExcludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator):
17
18
  """
18
19
  Applies a conditional OR on any number of filters comprised of either variables or scripts.
19
20
  If any filter evaluates to True, the entry will be excluded.
@@ -29,6 +30,8 @@ class FilterExcludeOptions(ListFormatterValidator, OptionsValidator):
29
30
  { %contains( %lower(description), '#short' ) }
30
31
  """
31
32
 
33
+ _inner_list_type = BooleanFormatterValidator
34
+
32
35
 
33
36
  class FilterExcludePlugin(Plugin[FilterExcludeOptions]):
34
37
  plugin_options_type = FilterExcludeOptions
@@ -52,7 +55,9 @@ class FilterExcludePlugin(Plugin[FilterExcludeOptions]):
52
55
  return entry
53
56
 
54
57
  for formatter in self.plugin_options.list:
55
- should_exclude = self.overrides.evaluate_boolean(formatter=formatter, entry=entry)
58
+ should_exclude = self.overrides.apply_formatter(
59
+ formatter=formatter, entry=entry, expected_type=bool
60
+ )
56
61
 
57
62
  if should_exclude:
58
63
  logger.info(
@@ -7,14 +7,14 @@ from ytdl_sub.config.validators.options import OptionsValidator
7
7
  from ytdl_sub.entries.entry import Entry
8
8
  from ytdl_sub.utils.exceptions import StringFormattingException
9
9
  from ytdl_sub.utils.logger import Logger
10
- from ytdl_sub.utils.script import ScriptUtils
11
- from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator
10
+ from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator
11
+ from ytdl_sub.validators.validators import ListValidator
12
12
  from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
13
13
 
14
14
  logger = Logger.get("filter-include")
15
15
 
16
16
 
17
- class FilterIncludeOptions(ListFormatterValidator, OptionsValidator):
17
+ class FilterIncludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator):
18
18
  """
19
19
  Applies a conditional AND on any number of filters comprised of either variables or scripts.
20
20
  If all filters evaluate to True, the entry will be included.
@@ -38,6 +38,8 @@ class FilterIncludeOptions(ListFormatterValidator, OptionsValidator):
38
38
  }
39
39
  """
40
40
 
41
+ _inner_list_type = BooleanFormatterValidator
42
+
41
43
 
42
44
  class FilterIncludePlugin(Plugin[FilterIncludeOptions]):
43
45
  plugin_options_type = FilterIncludeOptions
@@ -61,8 +63,8 @@ class FilterIncludePlugin(Plugin[FilterIncludeOptions]):
61
63
  return entry
62
64
 
63
65
  for formatter in self.plugin_options.list:
64
- should_exclude = ScriptUtils.bool_formatter_output(
65
- self.overrides.apply_formatter(formatter=formatter, entry=entry)
66
+ should_exclude = self.overrides.apply_formatter(
67
+ formatter=formatter, entry=entry, expected_type=bool
66
68
  )
67
69
  if not should_exclude:
68
70
  logger.info(
@@ -140,7 +140,7 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC):
140
140
  if not nfo_tags:
141
141
  return
142
142
 
143
- if self.overrides.evaluate_boolean(self.plugin_options.kodi_safe):
143
+ if self.overrides.apply_formatter(self.plugin_options.kodi_safe, expected_type=bool):
144
144
  nfo_root = to_max_3_byte_utf8_string(nfo_root)
145
145
  nfo_tags = {
146
146
  to_max_3_byte_utf8_string(key): [
@@ -31,7 +31,7 @@ class SquareThumbnailPlugin(Plugin[SquareThumbnailOptions]):
31
31
 
32
32
  @property
33
33
  def _square_thumbnail(self) -> bool:
34
- return self.overrides.evaluate_boolean(self.plugin_options)
34
+ return self.overrides.apply_formatter(self.plugin_options, expected_type=bool)
35
35
 
36
36
  @classmethod
37
37
  def _convert_to_square_thumbnail(cls, entry: Entry) -> None:
@@ -42,8 +42,8 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC):
42
42
  )
43
43
 
44
44
  def _randomized_float(self, overrides: Overrides, entry: Optional[Entry] = None) -> float:
45
- actualized_min = float(overrides.apply_formatter(self._min, entry=entry))
46
- actualized_max = float(overrides.apply_formatter(self._max, entry=entry))
45
+ actualized_min = overrides.apply_formatter(self._min, entry=entry, expected_type=float)
46
+ actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float)
47
47
 
48
48
  if actualized_min < 0:
49
49
  raise self._validation_exception(
@@ -70,7 +70,7 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC):
70
70
  -------
71
71
  Max possible value
72
72
  """
73
- actualized_max = float(overrides.apply_formatter(self._max, entry=entry))
73
+ actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float)
74
74
  if actualized_max < 0:
75
75
  raise self._validation_exception(
76
76
  f"max must be greater than zero, received {actualized_max}"
@@ -34,7 +34,7 @@ class VideoTagsPlugin(Plugin[VideoTagsOptions]):
34
34
  Tags the entry's audio file using values defined in the metadata options
35
35
  """
36
36
  tags_to_write: Dict[str, str] = {}
37
- for tag_name, tag_formatter in self.plugin_options.dict.items():
37
+ for tag_name, tag_formatter in sorted(self.plugin_options.dict.items()):
38
38
  tag_value = self.overrides.apply_formatter(formatter=tag_formatter, entry=entry)
39
39
  tags_to_write[tag_name] = tag_value
40
40
 
@@ -8,7 +8,7 @@ presets:
8
8
  urls:
9
9
  # The first URL will be all the artist's tracks.
10
10
  # Treat these as singles - an album with a single track
11
- - url: "{url}/tracks"
11
+ - url: "{url}"
12
12
  include_sibling_metadata: False
13
13
  variables:
14
14
  sc_track_album: "{title}"