ytdl-sub 2025.9.27.post4__tar.gz → 2025.11.7.post1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/PKG-INFO +4 -4
  2. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/pyproject.toml +3 -3
  3. ytdl_sub-2025.11.7.post1/src/ytdl_sub/__init__.py +1 -0
  4. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/cli/entrypoint.py +6 -1
  5. ytdl_sub-2025.11.7.post1/src/ytdl_sub/cli/parsers/cli_to_sub.py +64 -0
  6. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/cli/parsers/main.py +4 -0
  7. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/defaults.py +2 -0
  8. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/url/downloader.py +22 -21
  9. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/url/validators.py +17 -0
  10. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/script/variable_definitions.py +1 -0
  11. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/subtitles.py +32 -3
  12. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +201 -0
  13. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/utils/type_checking.py +2 -1
  14. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/ffmpeg.py +5 -1
  15. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub.egg-info/PKG-INFO +4 -4
  16. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub.egg-info/SOURCES.txt +1 -0
  17. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub.egg-info/requires.txt +3 -3
  18. ytdl_sub-2025.9.27.post4/src/ytdl_sub/__init__.py +0 -1
  19. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/LICENSE +0 -0
  20. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/README.md +0 -0
  21. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/setup.cfg +0 -0
  22. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/cli/__init__.py +0 -0
  23. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/cli/output_summary.py +0 -0
  24. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/cli/output_transaction_log.py +0 -0
  25. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/cli/parsers/__init__.py +0 -0
  26. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/cli/parsers/dl.py +0 -0
  27. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/__init__.py +0 -0
  28. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/config_file.py +0 -0
  29. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/config_validator.py +0 -0
  30. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/overrides.py +0 -0
  31. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/plugin/__init__.py +0 -0
  32. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/plugin/plugin.py +0 -0
  33. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/plugin/plugin_mapping.py +0 -0
  34. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/plugin/plugin_operation.py +0 -0
  35. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/plugin/preset_plugins.py +0 -0
  36. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/preset.py +0 -0
  37. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/preset_options.py +0 -0
  38. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/validators/__init__.py +0 -0
  39. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/validators/options.py +0 -0
  40. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/config/validators/variable_validation.py +0 -0
  41. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/__init__.py +0 -0
  42. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/info_json/__init__.py +0 -0
  43. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +0 -0
  44. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/source_plugin.py +0 -0
  45. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/url/__init__.py +0 -0
  46. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/ytdl_options_builder.py +0 -0
  47. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/downloaders/ytdlp.py +0 -0
  48. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/__init__.py +0 -0
  49. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/base_entry.py +0 -0
  50. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/entry.py +0 -0
  51. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/entry_parent.py +0 -0
  52. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/script/__init__.py +0 -0
  53. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/script/custom_functions.py +0 -0
  54. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/script/function_scripts.py +0 -0
  55. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/script/variable_types.py +0 -0
  56. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/variables/__init__.py +0 -0
  57. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/entries/variables/override_variables.py +0 -0
  58. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/main.py +0 -0
  59. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/__init__.py +0 -0
  60. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/audio_extract.py +0 -0
  61. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/chapters.py +0 -0
  62. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/date_range.py +0 -0
  63. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/embed_thumbnail.py +0 -0
  64. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/file_convert.py +0 -0
  65. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/filter_exclude.py +0 -0
  66. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/filter_include.py +0 -0
  67. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/format.py +0 -0
  68. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/internal/__init__.py +0 -0
  69. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/internal/view.py +0 -0
  70. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/match_filters.py +0 -0
  71. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/music_tags.py +0 -0
  72. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/nfo_tags.py +0 -0
  73. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/output_directory_nfo_tags.py +0 -0
  74. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/split_by_chapters.py +0 -0
  75. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/square_thumbnail.py +0 -0
  76. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/static_nfo_tags.py +0 -0
  77. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/throttle_protection.py +0 -0
  78. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/plugins/video_tags.py +0 -0
  79. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/__init__.py +0 -0
  80. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/__init__.py +0 -0
  81. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +0 -0
  82. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml +0 -0
  83. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml +0 -0
  84. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/media_quality.yaml +0 -0
  85. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/players.yaml +0 -0
  86. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +0 -0
  87. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml +0 -0
  88. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +0 -0
  89. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/internal/__init__.py +0 -0
  90. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/internal/view.yaml +0 -0
  91. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music/__init__.py +0 -0
  92. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +0 -0
  93. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +0 -0
  94. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music/singles.yaml +0 -0
  95. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +0 -0
  96. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music_videos/__init__.py +0 -0
  97. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_base.yaml +0 -0
  98. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_extras.yaml +0 -0
  99. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_videos.yaml +0 -0
  100. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +0 -0
  101. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +0 -0
  102. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +0 -0
  103. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +0 -0
  104. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +0 -0
  105. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/__init__.py +0 -0
  106. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/__init__.py +0 -0
  107. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/array_functions.py +0 -0
  108. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/boolean_functions.py +0 -0
  109. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/conditional_functions.py +0 -0
  110. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/date_functions.py +0 -0
  111. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/error_functions.py +0 -0
  112. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/json_functions.py +0 -0
  113. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/map_functions.py +0 -0
  114. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/numeric_functions.py +0 -0
  115. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/print_functions.py +0 -0
  116. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/regex_functions.py +0 -0
  117. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/functions/string_functions.py +0 -0
  118. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/parser.py +0 -0
  119. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/script.py +0 -0
  120. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/script_output.py +0 -0
  121. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/__init__.py +0 -0
  122. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/array.py +0 -0
  123. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/function.py +0 -0
  124. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/map.py +0 -0
  125. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/resolvable.py +0 -0
  126. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/syntax_tree.py +0 -0
  127. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/variable.py +0 -0
  128. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/types/variable_dependency.py +0 -0
  129. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/utils/__init__.py +0 -0
  130. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/utils/exception_formatters.py +0 -0
  131. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/utils/exceptions.py +0 -0
  132. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/script/utils/name_validation.py +0 -0
  133. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/subscriptions/__init__.py +0 -0
  134. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/subscriptions/base_subscription.py +0 -0
  135. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/subscriptions/subscription.py +0 -0
  136. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/subscriptions/subscription_download.py +0 -0
  137. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/subscriptions/subscription_validators.py +0 -0
  138. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +0 -0
  139. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/thread/__init__.py +0 -0
  140. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/thread/log_entries_downloaded_listener.py +0 -0
  141. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/__init__.py +0 -0
  142. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/chapters.py +0 -0
  143. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/datetime.py +0 -0
  144. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/exceptions.py +0 -0
  145. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/file_handler.py +0 -0
  146. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/file_lock.py +0 -0
  147. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/file_path.py +0 -0
  148. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/logger.py +0 -0
  149. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/retry.py +0 -0
  150. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/script.py +0 -0
  151. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/scriptable.py +0 -0
  152. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/subtitles.py +0 -0
  153. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/system.py +0 -0
  154. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/thumbnail.py +0 -0
  155. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/xml.py +0 -0
  156. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/utils/yaml.py +0 -0
  157. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/__init__.py +0 -0
  158. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/audo_codec_validator.py +0 -0
  159. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/file_path_validators.py +0 -0
  160. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/nfo_validators.py +0 -0
  161. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/regex_validator.py +0 -0
  162. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/source_variable_validator.py +0 -0
  163. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/strict_dict_validator.py +0 -0
  164. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/string_datetime.py +0 -0
  165. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/string_formatter_validators.py +0 -0
  166. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/string_select_validator.py +0 -0
  167. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/validators/validators.py +0 -0
  168. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/ytdl_additions/__init__.py +0 -0
  169. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +0 -0
  170. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub.egg-info/dependency_links.txt +0 -0
  171. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/src/ytdl_sub.egg-info/entry_points.txt +0 -0
  172. {ytdl_sub-2025.9.27.post4 → ytdl_sub-2025.11.7.post1}/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.9.27.post4
3
+ Version: 2025.11.7.post1
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.9.26
692
+ Requires-Dist: yt-dlp[default]==2025.10.22
693
693
  Requires-Dist: colorama~=0.4
694
694
  Requires-Dist: mergedeep~=1.3
695
695
  Requires-Dist: mediafile~=0.12
@@ -700,8 +700,8 @@ Requires-Dist: pytest<9.0,>=7.2; extra == "test"
700
700
  Requires-Dist: pytest-rerunfailures<17,>=14; extra == "test"
701
701
  Provides-Extra: lint
702
702
  Requires-Dist: black==24.10.0; extra == "lint"
703
- Requires-Dist: isort==6.0.1; extra == "lint"
704
- Requires-Dist: pylint==3.3.8; extra == "lint"
703
+ Requires-Dist: isort==7.0.0; extra == "lint"
704
+ Requires-Dist: pylint==4.0.1; extra == "lint"
705
705
  Provides-Extra: docs
706
706
  Requires-Dist: sphinx<9,>=7; extra == "docs"
707
707
  Requires-Dist: sphinx-rtd-theme<4,>=2; extra == "docs"
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3.11",
16
16
  ]
17
17
  dependencies = [
18
- "yt-dlp[default]==2025.9.26",
18
+ "yt-dlp[default]==2025.10.22",
19
19
  "colorama~=0.4",
20
20
  "mergedeep~=1.3",
21
21
  "mediafile~=0.12",
@@ -48,8 +48,8 @@ test = [
48
48
  ]
49
49
  lint = [
50
50
  "black==24.10.0",
51
- "isort==6.0.1",
52
- "pylint==3.3.8",
51
+ "isort==7.0.0",
52
+ "pylint==4.0.1",
53
53
  ]
54
54
  docs = [
55
55
  "sphinx>=7,<9",
@@ -0,0 +1 @@
1
+ __pypi_version__ = "2025.11.07.post1";__local_version__ = "2025.11.07+e507e6e"
@@ -12,6 +12,7 @@ from yt_dlp.utils import sanitize_filename
12
12
  from ytdl_sub.cli.output_summary import output_summary
13
13
  from ytdl_sub.cli.output_transaction_log import _maybe_validate_transaction_log_file
14
14
  from ytdl_sub.cli.output_transaction_log import output_transaction_log
15
+ from ytdl_sub.cli.parsers.cli_to_sub import print_cli_to_sub
15
16
  from ytdl_sub.cli.parsers.dl import DownloadArgsParser
16
17
  from ytdl_sub.cli.parsers.main import DEFAULT_CONFIG_FILE_NAME
17
18
  from ytdl_sub.cli.parsers.main import parser
@@ -206,6 +207,10 @@ def main() -> List[Subscription]:
206
207
 
207
208
  args, extra_args = parser.parse_known_args()
208
209
 
210
+ if args.subparser == "cli-to-sub":
211
+ print_cli_to_sub(args=extra_args)
212
+ return []
213
+
209
214
  # Load the config
210
215
  if args.config:
211
216
  config = ConfigFile.from_file_path(args.config)
@@ -263,7 +268,7 @@ def main() -> List[Subscription]:
263
268
  _view_url_from_cli(config=config, url=args.url, split_chapters=args.split_chapters)
264
269
  )
265
270
  else:
266
- raise ValidationException("Must provide one of the commands: sub, dl, view")
271
+ raise ValidationException("Must provide one of the commands: sub, dl, view, cli-to-sub")
267
272
 
268
273
  if not args.suppress_transaction_log:
269
274
  output_transaction_log(
@@ -0,0 +1,64 @@
1
+ from typing import List
2
+
3
+ import yt_dlp
4
+ import yt_dlp.options
5
+
6
+ from ytdl_sub.utils.logger import Logger
7
+ from ytdl_sub.utils.yaml import dump_yaml
8
+
9
+ logger = Logger.get()
10
+
11
+ # pylint: disable=missing-function-docstring
12
+
13
+ ##############################################################
14
+ # --- BEGIN ----
15
+ # Copy of https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py
16
+
17
+ create_parser = yt_dlp.options.create_parser
18
+
19
+
20
+ def parse_patched_options(opts):
21
+
22
+ patched_parser = create_parser()
23
+ patched_parser.defaults.update(
24
+ {
25
+ "ignoreerrors": False,
26
+ "retries": 0,
27
+ "fragment_retries": 0,
28
+ "extract_flat": False,
29
+ "concat_playlist": "never",
30
+ "update_self": False,
31
+ }
32
+ )
33
+ yt_dlp.options.create_parser = lambda: patched_parser
34
+ try:
35
+ return yt_dlp.parse_options(opts)
36
+ finally:
37
+ yt_dlp.options.create_parser = create_parser
38
+
39
+
40
+ default_opts = parse_patched_options([]).ydl_opts
41
+
42
+
43
+ def cli_to_api(opts, cli_defaults=False):
44
+ opts = (yt_dlp.parse_options if cli_defaults else parse_patched_options)(opts).ydl_opts
45
+
46
+ diff = {k: v for k, v in opts.items() if default_opts[k] != v}
47
+ if "postprocessors" in diff:
48
+ diff["postprocessors"] = [
49
+ pp for pp in diff["postprocessors"] if pp not in default_opts["postprocessors"]
50
+ ]
51
+ return diff
52
+
53
+
54
+ # --- END ----
55
+ ##############################################################
56
+
57
+
58
+ def print_cli_to_sub(args: List[str]) -> None:
59
+ api_args = cli_to_api(args)
60
+ if not api_args:
61
+ logger.info("Does not resolve to any yt-dlp args")
62
+ return
63
+
64
+ print(dump_yaml({"ytdl_options": api_args}))
@@ -221,3 +221,7 @@ view_parser.add_argument(
221
221
  help="View source variables after splitting by chapters",
222
222
  )
223
223
  view_parser.add_argument("url", help="URL to view source variables for")
224
+
225
+ ###################################################################################################
226
+ # CLI-TO-SUB PARSER
227
+ cli_to_sub_parser = subparsers.add_parser("cli-to-sub")
@@ -2,6 +2,8 @@ import os
2
2
 
3
3
  from ytdl_sub.utils.system import IS_WINDOWS
4
4
 
5
+ # pylint: disable=invalid-name
6
+
5
7
 
6
8
  def _existing_path(*paths: str) -> str:
7
9
  """
@@ -257,6 +257,16 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
257
257
  .to_dict()
258
258
  )
259
259
 
260
+ def webpage_url(self, entry: Entry) -> str:
261
+ """
262
+ Returns
263
+ -------
264
+ The webpage_url to use for the actual download
265
+ """
266
+ url_idx = entry.get(v.ytdl_sub_input_url_index, int)
267
+ webpage_url_formatter = self.plugin_options.urls.list[url_idx].webpage_url
268
+ return self.overrides.apply_formatter(webpage_url_formatter, entry=entry)
269
+
260
270
  def metadata_ytdl_options(self, ytdl_option_overrides: Dict) -> Dict:
261
271
  """
262
272
  Returns
@@ -358,7 +368,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
358
368
  if (self.is_dry_run or not self.is_entry_thumbnails_enabled)
359
369
  else entry.is_thumbnail_downloaded_via_ytdlp
360
370
  ),
361
- url=entry.webpage_url,
371
+ url=self.webpage_url(entry=entry),
362
372
  )
363
373
  return Entry(
364
374
  download_entry_dict,
@@ -366,13 +376,13 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
366
376
  )
367
377
 
368
378
  def _iterate_child_entries(
369
- self, entries: List[Entry], download_reversed: bool
379
+ self, entries: List[Entry], validator: UrlValidator
370
380
  ) -> Iterator[Entry]:
371
381
  # Iterate a list of entries, and delete the entries after yielding
372
382
  entries_to_iter: List[Optional[Entry]] = entries
373
383
 
374
384
  indices = list(range(len(entries_to_iter)))
375
- if download_reversed:
385
+ if self.overrides.evaluate_boolean(validator.download_reverse):
376
386
  indices = reversed(indices)
377
387
 
378
388
  for idx in indices:
@@ -394,17 +404,13 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
394
404
  entries_to_iter[idx] = None
395
405
 
396
406
  def _iterate_parent_entry(
397
- self, parent: EntryParent, download_reversed: bool
407
+ self, parent: EntryParent, validator: UrlValidator
398
408
  ) -> Iterator[Entry]:
399
- yield from self._iterate_child_entries(
400
- entries=parent.entry_children(), download_reversed=download_reversed
401
- )
409
+ yield from self._iterate_child_entries(entries=parent.entry_children(), validator=validator)
402
410
 
403
411
  # Recursion the parent's parent entries
404
412
  for parent_child in reversed(parent.parent_children()):
405
- yield from self._iterate_parent_entry(
406
- parent=parent_child, download_reversed=download_reversed
407
- )
413
+ yield from self._iterate_parent_entry(parent=parent_child, validator=validator)
408
414
 
409
415
  def _download_url_metadata(
410
416
  self, url: str, include_sibling_metadata: bool, ytdl_options_overrides: Dict
@@ -438,7 +444,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
438
444
  self,
439
445
  parents: List[EntryParent],
440
446
  orphans: List[Entry],
441
- download_reversed: bool,
447
+ validator: UrlValidator,
442
448
  ) -> Iterator[Entry]:
443
449
  """
444
450
  Downloads the leaf entries from EntryParent trees
@@ -446,19 +452,15 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
446
452
  # Delete info json files afterwards so other collection URLs do not use them
447
453
  with self._separate_download_archives(clear_info_json_files=True):
448
454
  for parent in parents:
449
- yield from self._iterate_parent_entry(
450
- parent=parent, download_reversed=download_reversed
451
- )
455
+ yield from self._iterate_parent_entry(parent=parent, validator=validator)
452
456
 
453
- yield from self._iterate_child_entries(
454
- entries=orphans, download_reversed=download_reversed
455
- )
457
+ yield from self._iterate_child_entries(entries=orphans, validator=validator)
456
458
 
457
459
  def _download_metadata(self, url: str, validator: UrlValidator) -> Iterable[Entry]:
458
460
  metadata_ytdl_options = self.metadata_ytdl_options(
459
461
  ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides)
460
462
  )
461
- download_reversed = self.overrides.evaluate_boolean(validator.download_reverse)
463
+
462
464
  include_sibling_metadata = self.overrides.evaluate_boolean(
463
465
  validator.include_sibling_metadata
464
466
  )
@@ -469,16 +471,15 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
469
471
  ytdl_options_overrides=metadata_ytdl_options,
470
472
  )
471
473
 
472
- # TODO: Encapsulate this logic into its own class
473
474
  self._url_state = URLDownloadState(
474
- entries_total=sum(parent.num_children() for parent in parents) + len(orphan_entries)
475
+ entries_total=sum(parent.num_children() for parent in parents) + len(orphan_entries),
475
476
  )
476
477
 
477
478
  download_logger.info("Beginning downloads for %s", url)
478
479
  yield from self._iterate_entries(
479
480
  parents=parents,
480
481
  orphans=orphan_entries,
481
- download_reversed=download_reversed,
482
+ validator=validator,
482
483
  )
483
484
 
484
485
  def download_metadata(self) -> Iterable[Entry]:
@@ -52,6 +52,7 @@ class UrlValidator(StrictDictValidator):
52
52
  "download_reverse",
53
53
  "ytdl_options",
54
54
  "include_sibling_metadata",
55
+ "webpage_url",
55
56
  }
56
57
 
57
58
  @classmethod
@@ -89,6 +90,9 @@ class UrlValidator(StrictDictValidator):
89
90
  validator=OverridesBooleanFormatterValidator,
90
91
  default="False",
91
92
  )
93
+ self._webpage_url = self._validate_key(
94
+ key="webpage_url", validator=StringFormatterValidator, default="{webpage_url}"
95
+ )
92
96
 
93
97
  @property
94
98
  def url(self) -> OverridesStringFormatterValidator:
@@ -180,6 +184,19 @@ class UrlValidator(StrictDictValidator):
180
184
  """
181
185
  return self._include_sibling_metadata
182
186
 
187
+ @property
188
+ def webpage_url(self) -> StringFormatterValidator:
189
+ """
190
+ Optional. After ytdl-sub performs the metadata download, it will inspect each
191
+ entry's .info.json file and perform the actual download from yt-dlp using
192
+ `webpage_url <config_reference/scripting/entry_variables:webpage_url>`. This
193
+ can be overwritten by supplying parameter with a modification to ``webpage_url`` in the
194
+ form of an override variable.
195
+
196
+ Defaults to ``{webpage_url}``.
197
+ """
198
+ return self._webpage_url
199
+
183
200
 
184
201
  class UrlStringOrDictValidator(UrlValidator):
185
202
  """
@@ -1162,6 +1162,7 @@ class VariableDefinitions(
1162
1162
  """
1163
1163
  return {
1164
1164
  self.uid,
1165
+ self.extractor,
1165
1166
  self.extractor_key,
1166
1167
  self.epoch,
1167
1168
  self.webpage_url,
@@ -1,8 +1,10 @@
1
+ import os
1
2
  from pathlib import Path
2
3
  from typing import Dict
3
4
  from typing import List
4
5
  from typing import Optional
5
6
  from typing import Set
7
+ from typing import Tuple
6
8
 
7
9
  from ytdl_sub.config.plugin.plugin import Plugin
8
10
  from ytdl_sub.config.plugin.plugin_operation import PluginOperation
@@ -11,6 +13,7 @@ from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder
11
13
  from ytdl_sub.entries.entry import Entry
12
14
  from ytdl_sub.entries.script.variable_definitions import VARIABLES
13
15
  from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
16
+ from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError
14
17
  from ytdl_sub.utils.file_handler import FileHandler
15
18
  from ytdl_sub.utils.file_handler import FileMetadata
16
19
  from ytdl_sub.utils.logger import Logger
@@ -57,6 +60,7 @@ class SubtitleOptions(ToggleableOptionsDictValidator):
57
60
  "subtitles_type",
58
61
  "embed_subtitles",
59
62
  "languages",
63
+ "languages_required",
60
64
  "allow_auto_generated_subtitles",
61
65
  }
62
66
 
@@ -76,6 +80,9 @@ class SubtitleOptions(ToggleableOptionsDictValidator):
76
80
  self._languages = self._validate_key_if_present(
77
81
  key="languages", validator=StringListValidator, default=["en"]
78
82
  ).list
83
+ self._languages_required = self._validate_key_if_present(
84
+ key="languages_required", validator=StringListValidator, default=[]
85
+ ).list
79
86
  self._allow_auto_generated_subtitles = self._validate_key_if_present(
80
87
  key="allow_auto_generated_subtitles", validator=BoolValidator, default=False
81
88
  ).value
@@ -121,6 +128,16 @@ class SubtitleOptions(ToggleableOptionsDictValidator):
121
128
  """
122
129
  return [lang.value for lang in self._languages]
123
130
 
131
+ @property
132
+ def languages_required(self) -> Optional[List[str]]:
133
+ """
134
+ :expected type: Optional[List[String]]
135
+ :description:
136
+ Language code(s) that are required to be present for downloads to continue. If missing,
137
+ ytdl-sub will throw an error. NOTE: currently this only checks file-based subtitles.
138
+ """
139
+ return [lang.value for lang in self._languages_required]
140
+
124
141
  @property
125
142
  def allow_auto_generated_subtitles(self) -> Optional[bool]:
126
143
  """
@@ -211,7 +228,21 @@ class SubtitlesPlugin(Plugin[SubtitleOptions]):
211
228
  if self.plugin_options.embed_subtitles:
212
229
  file_metadata = FileMetadata(f"Embedded subtitles with lang(s) {', '.join(langs)}")
213
230
  if self.plugin_options.subtitles_name:
231
+ download_subtitle_lang_file_names: List[Tuple[str, str]] = []
232
+
214
233
  for lang in langs:
234
+ download_subtitle_file_name = entry.base_filename(
235
+ ext=f"{lang}.{self.plugin_options.subtitles_type}"
236
+ )
237
+ if os.path.isfile(Path(self.working_directory) / download_subtitle_file_name):
238
+ download_subtitle_lang_file_names.append((lang, download_subtitle_file_name))
239
+ elif lang in self.plugin_options.languages_required:
240
+ raise UserThrownRuntimeError(
241
+ f"Required the subtitle lang {lang}, but the file could not be found for "
242
+ f"the entry {entry.title}."
243
+ )
244
+
245
+ for lang, file_name in download_subtitle_lang_file_names:
215
246
  output_subtitle_file_name = self.overrides.apply_formatter(
216
247
  formatter=self.plugin_options.subtitles_name,
217
248
  entry=entry,
@@ -219,9 +250,7 @@ class SubtitlesPlugin(Plugin[SubtitleOptions]):
219
250
  )
220
251
 
221
252
  self.save_file(
222
- file_name=entry.base_filename(
223
- ext=f"{lang}.{self.plugin_options.subtitles_type}"
224
- ),
253
+ file_name=file_name,
225
254
  output_file_name=output_subtitle_file_name,
226
255
  entry=entry,
227
256
  )