subliminal 2.4.0__tar.gz → 2.5.0__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 (318) hide show
  1. {subliminal-2.4.0 → subliminal-2.5.0}/.pre-commit-config.yaml +4 -4
  2. {subliminal-2.4.0 → subliminal-2.5.0}/HISTORY.rst +27 -0
  3. {subliminal-2.4.0 → subliminal-2.5.0}/PKG-INFO +3 -2
  4. {subliminal-2.4.0 → subliminal-2.5.0}/docs/user/configuration_file.rst +4 -0
  5. {subliminal-2.4.0 → subliminal-2.5.0}/hatch.toml +1 -1
  6. {subliminal-2.4.0 → subliminal-2.5.0}/pyproject.toml +2 -1
  7. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cli/generate_config.py +59 -28
  8. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/core.py +2 -2
  9. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/extensions.py +35 -11
  10. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/opensubtitles.py +5 -3
  11. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/opensubtitlescom.py +21 -6
  12. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/score.py +1 -1
  13. subliminal-2.5.0/tests/cassettes/opensubtitlescom/test_query_max_result_pages.yaml +10748 -0
  14. subliminal-2.5.0/tests/cassettes/tmdb/test_search_movie_page.yaml +313 -0
  15. subliminal-2.5.0/tests/cli/test_cli.py +34 -0
  16. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cli/test_download.py +102 -110
  17. subliminal-2.5.0/tests/cli/test_generate_config.py +57 -0
  18. {subliminal-2.4.0 → subliminal-2.5.0}/tests/conftest.py +223 -4
  19. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_opensubtitlescom.py +15 -0
  20. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_providers.py +23 -19
  21. {subliminal-2.4.0 → subliminal-2.5.0}/tests/refiners/test_tmdb.py +8 -4
  22. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_archives.py +4 -3
  23. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_core.py +14 -3
  24. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_provider_pool.py +19 -17
  25. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_subtitle.py +1 -1
  26. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_video.py +2 -1
  27. subliminal-2.4.0/tests/cassettes/tmdb/test_search_movie_page.yaml +0 -146
  28. subliminal-2.4.0/tests/cli/test_cli.py +0 -32
  29. subliminal-2.4.0/tests/cli/test_generate_config.py +0 -33
  30. {subliminal-2.4.0 → subliminal-2.5.0}/.dockerignore +0 -0
  31. {subliminal-2.4.0 → subliminal-2.5.0}/.github/actions/prepare-tests/action.yaml +0 -0
  32. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/CI.yaml +0 -0
  33. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/coverage.yaml +0 -0
  34. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/docker-build.yaml +0 -0
  35. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/prepare-release-pr.yaml +0 -0
  36. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/publish-release.yaml +0 -0
  37. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/publish.yaml +0 -0
  38. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/scheduled-tests.yaml +0 -0
  39. {subliminal-2.4.0 → subliminal-2.5.0}/.github/workflows/tag-release.yaml +0 -0
  40. {subliminal-2.4.0 → subliminal-2.5.0}/.github/zizmor.yml +0 -0
  41. {subliminal-2.4.0 → subliminal-2.5.0}/.gitignore +0 -0
  42. {subliminal-2.4.0 → subliminal-2.5.0}/.python-version-default +0 -0
  43. {subliminal-2.4.0 → subliminal-2.5.0}/.readthedocs.yaml +0 -0
  44. {subliminal-2.4.0 → subliminal-2.5.0}/CONTRIBUTING.md +0 -0
  45. {subliminal-2.4.0 → subliminal-2.5.0}/Dockerfile +0 -0
  46. {subliminal-2.4.0 → subliminal-2.5.0}/LICENSE +0 -0
  47. {subliminal-2.4.0 → subliminal-2.5.0}/MANIFEST.in +0 -0
  48. {subliminal-2.4.0 → subliminal-2.5.0}/README.rst +0 -0
  49. {subliminal-2.4.0 → subliminal-2.5.0}/RELEASING.md +0 -0
  50. {subliminal-2.4.0 → subliminal-2.5.0}/changelog.d/.gitignore +0 -0
  51. {subliminal-2.4.0 → subliminal-2.5.0}/docs/Makefile +0 -0
  52. {subliminal-2.4.0 → subliminal-2.5.0}/docs/_intersphinx/README.md +0 -0
  53. {subliminal-2.4.0 → subliminal-2.5.0}/docs/_intersphinx/importlib_metadata_objects.inv +0 -0
  54. {subliminal-2.4.0 → subliminal-2.5.0}/docs/_intersphinx/importlib_metadata_objects.txt +0 -0
  55. {subliminal-2.4.0 → subliminal-2.5.0}/docs/_static/.gitignore +0 -0
  56. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/cache.rst +0 -0
  57. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/cli.rst +0 -0
  58. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/core.rst +0 -0
  59. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/exceptions.rst +0 -0
  60. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/extensions.rst +0 -0
  61. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/providers.rst +0 -0
  62. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/refiners.rst +0 -0
  63. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/score.rst +0 -0
  64. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/subtitle.rst +0 -0
  65. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/utils.rst +0 -0
  66. {subliminal-2.4.0 → subliminal-2.5.0}/docs/api/video.rst +0 -0
  67. {subliminal-2.4.0 → subliminal-2.5.0}/docs/cassettes/test_usage.yaml +0 -0
  68. {subliminal-2.4.0 → subliminal-2.5.0}/docs/conf.py +0 -0
  69. {subliminal-2.4.0 → subliminal-2.5.0}/docs/config.toml +0 -0
  70. {subliminal-2.4.0 → subliminal-2.5.0}/docs/conftest.py +0 -0
  71. {subliminal-2.4.0 → subliminal-2.5.0}/docs/index.rst +0 -0
  72. {subliminal-2.4.0 → subliminal-2.5.0}/docs/make.bat +0 -0
  73. {subliminal-2.4.0 → subliminal-2.5.0}/docs/user/changelog.rst +0 -0
  74. {subliminal-2.4.0 → subliminal-2.5.0}/docs/user/cli.rst +0 -0
  75. {subliminal-2.4.0 → subliminal-2.5.0}/docs/user/how_it_works.rst +0 -0
  76. {subliminal-2.4.0 → subliminal-2.5.0}/docs/user/installation.rst +0 -0
  77. {subliminal-2.4.0 → subliminal-2.5.0}/docs/user/provider_guide.rst +0 -0
  78. {subliminal-2.4.0 → subliminal-2.5.0}/docs/user/usage.rst +0 -0
  79. {subliminal-2.4.0 → subliminal-2.5.0}/scripts/.gitignore +0 -0
  80. {subliminal-2.4.0 → subliminal-2.5.0}/scripts/prepare-release-pr.py +0 -0
  81. {subliminal-2.4.0 → subliminal-2.5.0}/scripts/prepare_tests.py +0 -0
  82. {subliminal-2.4.0 → subliminal-2.5.0}/scripts/release.py +0 -0
  83. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/__init__.py +0 -0
  84. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/__main__.py +0 -0
  85. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/archives.py +0 -0
  86. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cache.py +0 -0
  87. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cli/__init__.py +0 -0
  88. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cli/cli.py +0 -0
  89. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cli/commands/__init__.py +0 -0
  90. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cli/commands/_format.py +0 -0
  91. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cli/commands/download_best.py +0 -0
  92. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/cli/helpers.py +0 -0
  93. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/converters/__init__.py +0 -0
  94. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/converters/addic7ed.py +0 -0
  95. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/converters/opensubtitles.py +0 -0
  96. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/converters/opensubtitlescom.py +0 -0
  97. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/converters/subtitulamos.py +0 -0
  98. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/converters/tvsubtitles.py +0 -0
  99. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/exceptions.py +0 -0
  100. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/matches.py +0 -0
  101. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/__init__.py +0 -0
  102. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/addic7ed.py +0 -0
  103. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/bsplayer.py +0 -0
  104. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/gestdown.py +0 -0
  105. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/mock.py +0 -0
  106. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/napiprojekt.py +0 -0
  107. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/podnapisi.py +0 -0
  108. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/subtitulamos.py +0 -0
  109. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/providers/tvsubtitles.py +0 -0
  110. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/py.typed +0 -0
  111. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/refiners/__init__.py +0 -0
  112. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/refiners/hash.py +0 -0
  113. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/refiners/metadata.py +0 -0
  114. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/refiners/omdb.py +0 -0
  115. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/refiners/tmdb.py +0 -0
  116. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/refiners/tvdb.py +0 -0
  117. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/subtitle.py +0 -0
  118. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/utils.py +0 -0
  119. {subliminal-2.4.0 → subliminal-2.5.0}/src/subliminal/video.py +0 -0
  120. {subliminal-2.4.0 → subliminal-2.5.0}/tests/__init__.py +0 -0
  121. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_download_subtitle.yaml +0 -0
  122. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_get_show_id.yaml +0 -0
  123. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_get_show_id_country.yaml +0 -0
  124. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_get_show_id_quote_dots_mixed_case.yaml +0 -0
  125. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_get_show_id_with_comma.yaml +0 -0
  126. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_get_show_id_year.yaml +0 -0
  127. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_get_show_ids.yaml +0 -0
  128. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_list_subtitles.yaml +0 -0
  129. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_list_subtitles_episode_alternative_series.yaml +0 -0
  130. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_list_subtitles_show_with_asterisk.yaml +0 -0
  131. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query.yaml +0 -0
  132. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query_no_year.yaml +0 -0
  133. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query_parsing.yaml +0 -0
  134. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query_parsing_colon.yaml +0 -0
  135. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query_parsing_dash.yaml +0 -0
  136. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query_parsing_quote_dots_mixed_case.yaml +0 -0
  137. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query_wrong_series.yaml +0 -0
  138. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/addic7ed/test_query_year.yaml +0 -0
  139. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/bsplayer/test_download_subtitle.yaml +0 -0
  140. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/bsplayer/test_list_subtitles_hash.yaml +0 -0
  141. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/bsplayer/test_login.yaml +0 -0
  142. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/bsplayer/test_logout.yaml +0 -0
  143. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/bsplayer/test_query_hash_size.yaml +0 -0
  144. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/cli/test_cli_download.yaml +0 -0
  145. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_download_subtitle.yaml +0 -0
  146. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_download_with_bom.yaml +0 -0
  147. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_get_title_and_show_id_alternative_name.yaml +0 -0
  148. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_get_title_and_show_id_no_year.yaml +0 -0
  149. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_get_title_and_show_id_only_title.yaml +0 -0
  150. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_get_title_and_show_id_with_tvdb_id.yaml +0 -0
  151. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_list_subtitles.yaml +0 -0
  152. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_list_subtitles_episode_alternative_series.yaml +0 -0
  153. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query.yaml +0 -0
  154. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query_all_series.yaml +0 -0
  155. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query_no_language.yaml +0 -0
  156. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query_parsing.yaml +0 -0
  157. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query_parsing_colon.yaml +0 -0
  158. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query_parsing_dash.yaml +0 -0
  159. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query_parsing_quote_dots_mixed_case.yaml +0 -0
  160. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_query_wrong_series.yaml +0 -0
  161. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_search_show_id.yaml +0 -0
  162. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_search_show_id_error.yaml +0 -0
  163. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_search_show_id_incomplete.yaml +0 -0
  164. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_search_show_id_no_year.yaml +0 -0
  165. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_search_show_id_quote.yaml +0 -0
  166. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_search_show_series_tvdb_id.yaml +0 -0
  167. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/gestdown/test_show_with_asterisk.yaml +0 -0
  168. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/napiprojekt/test_download_subtitles.yaml +0 -0
  169. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/napiprojekt/test_list_subtitles.yaml +0 -0
  170. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/napiprojekt/test_query_microdvd.yaml +0 -0
  171. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/napiprojekt/test_query_srt.yaml +0 -0
  172. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/napiprojekt/test_query_srt_reencode.yaml +0 -0
  173. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/napiprojekt/test_query_wrong_hash.yaml +0 -0
  174. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_get_id.yaml +0 -0
  175. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_get_title.yaml +0 -0
  176. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_get_wrong_id.yaml +0 -0
  177. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_get_wrong_title.yaml +0 -0
  178. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_refine_episode.yaml +0 -0
  179. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_refine_episode_original_series.yaml +0 -0
  180. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_refine_episode_with_country.yaml +0 -0
  181. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
  182. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_refine_episode_year.yaml +0 -0
  183. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_refine_movie.yaml +0 -0
  184. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_refine_movie_guess_alternative_title.yaml +0 -0
  185. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_search.yaml +0 -0
  186. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_search_page.yaml +0 -0
  187. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_search_type.yaml +0 -0
  188. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_search_wrong_title.yaml +0 -0
  189. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/omdb/test_search_year.yaml +0 -0
  190. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_download_subtitle.yaml +0 -0
  191. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_list_subtitles_episode.yaml +0 -0
  192. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_list_subtitles_movie.yaml +0 -0
  193. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_list_subtitles_movie_no_hash.yaml +0 -0
  194. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_login_bad_password.yaml +0 -0
  195. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_login_vip_bad_password.yaml +0 -0
  196. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_login_vip_login.yaml +0 -0
  197. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_no_operation.yaml +0 -0
  198. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_query_hash_size.yaml +0 -0
  199. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_query_imdb_id.yaml +0 -0
  200. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_query_not_enough_information.yaml +0 -0
  201. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_query_query_episode.yaml +0 -0
  202. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_query_query_movie.yaml +0 -0
  203. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_query_query_season_episode.yaml +0 -0
  204. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitles/test_query_wrong_hash_wrong_size.yaml +0 -0
  205. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_download_subtitle.yaml +0 -0
  206. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_episode.yaml +0 -0
  207. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_movie.yaml +0 -0
  208. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_movie_no_hash.yaml +0 -0
  209. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_login.yaml +0 -0
  210. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_login_bad_password.yaml +0 -0
  211. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_logout.yaml +0 -0
  212. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_query_hash_size.yaml +0 -0
  213. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_query_imdb_id.yaml +0 -0
  214. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_query_query_episode.yaml +0 -0
  215. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_query_query_movie.yaml +0 -0
  216. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_query_query_season_episode.yaml +0 -0
  217. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_query_tag_movie.yaml +0 -0
  218. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_query_wrong_hash_wrong_size.yaml +0 -0
  219. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_tag_match.yaml +0 -0
  220. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/opensubtitlescom/test_user_infos.yaml +0 -0
  221. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/podnapisi/test_download_subtitle.yaml +0 -0
  222. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/podnapisi/test_download_subtitles_with_title_unicode.yaml +0 -0
  223. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/podnapisi/test_list_subtitles_episode.yaml +0 -0
  224. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/podnapisi/test_list_subtitles_episode_alternative_series.yaml +0 -0
  225. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/podnapisi/test_list_subtitles_movie.yaml +0 -0
  226. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/podnapisi/test_query_episode.yaml +0 -0
  227. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/podnapisi/test_query_movie.yaml +0 -0
  228. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/providers/test_download_best_subtitles.yaml +0 -0
  229. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/providers/test_download_best_subtitles_min_score.yaml +0 -0
  230. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/providers/test_download_best_subtitles_only_one.yaml +0 -0
  231. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/providers/test_list_subtitles_providers_download.yaml +0 -0
  232. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_download_subtitle.yaml +0 -0
  233. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_download_subtitle_first_episode.yaml +0 -0
  234. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_download_subtitle_foo.yaml +0 -0
  235. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_download_subtitle_last_season.yaml +0 -0
  236. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_download_subtitle_year.yaml +0 -0
  237. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_episode.yaml +0 -0
  238. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_language.yaml +0 -0
  239. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_season.yaml +0 -0
  240. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_series.yaml +0 -0
  241. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/subtitulamos/test_list_subtitles_with_spanish_non_mx_search.yaml +0 -0
  242. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_get_movie_id.yaml +0 -0
  243. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_get_movie_id_failed.yaml +0 -0
  244. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_get_series_id.yaml +0 -0
  245. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_get_series_wrong_id.yaml +0 -0
  246. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_refine_episode.yaml +0 -0
  247. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_refine_episode_original_series.yaml +0 -0
  248. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_refine_episode_with_country.yaml +0 -0
  249. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
  250. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_refine_episode_year.yaml +0 -0
  251. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_refine_movie.yaml +0 -0
  252. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_refine_movie_guess_alternative_title.yaml +0 -0
  253. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_search_movie.yaml +0 -0
  254. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_search_movie_wrong_title.yaml +0 -0
  255. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_search_movie_year.yaml +0 -0
  256. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_search_series.yaml +0 -0
  257. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tmdb/test_search_series_wrong_name.yaml +0 -0
  258. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_get_series.yaml +0 -0
  259. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_get_series_actors.yaml +0 -0
  260. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_get_series_actors_wrong_id.yaml +0 -0
  261. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_get_series_wrong_id.yaml +0 -0
  262. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_login.yaml +0 -0
  263. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_login_error.yaml +0 -0
  264. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_query_series_episodes.yaml +0 -0
  265. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_query_series_episodes_wrong_season.yaml +0 -0
  266. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine.yaml +0 -0
  267. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_ambiguous.yaml +0 -0
  268. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_ambiguous_2.yaml +0 -0
  269. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_episode_alternative_series.yaml +0 -0
  270. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_episode_no_year.yaml +0 -0
  271. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_episode_partial.yaml +0 -0
  272. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_episode_with_comma.yaml +0 -0
  273. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_episode_with_country.yaml +0 -0
  274. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
  275. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refine_episode_year.yaml +0 -0
  276. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_refresh_token.yaml +0 -0
  277. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_search_series.yaml +0 -0
  278. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_search_series_wrong_name.yaml +0 -0
  279. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvdb/test_token_needs_refresh.yaml +0 -0
  280. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_download_subtitle.yaml +0 -0
  281. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_download_subtitle_with_quote.yaml +0 -0
  282. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_get_episode_ids.yaml +0 -0
  283. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_get_episode_ids_wrong_season.yaml +0 -0
  284. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_list_subtitles.yaml +0 -0
  285. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_list_subtitles_episode_alternative_series.yaml +0 -0
  286. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_query.yaml +0 -0
  287. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_query_no_year.yaml +0 -0
  288. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_query_with_quote.yaml +0 -0
  289. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_query_wrong_episode.yaml +0 -0
  290. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_query_wrong_series.yaml +0 -0
  291. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id.yaml +0 -0
  292. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id_ambiguous.yaml +0 -0
  293. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id_error.yaml +0 -0
  294. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id_incomplete.yaml +0 -0
  295. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id_no_year.yaml +0 -0
  296. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id_uk.yaml +0 -0
  297. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id_us.yaml +0 -0
  298. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cassettes/tvsubtitles/test_search_show_id_year_in_title.yaml +0 -0
  299. {subliminal-2.4.0 → subliminal-2.5.0}/tests/cli/test_helpers.py +0 -0
  300. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_addic7ed.py +0 -0
  301. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_bsplayer.py +0 -0
  302. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_gestdown.py +0 -0
  303. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_napiprojekt.py +0 -0
  304. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_opensubtitles.py +0 -0
  305. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_podnapisi.py +0 -0
  306. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_subtitulamos.py +0 -0
  307. {subliminal-2.4.0 → subliminal-2.5.0}/tests/providers/test_tvsubtitles.py +0 -0
  308. {subliminal-2.4.0 → subliminal-2.5.0}/tests/refiners/test_hash.py +0 -0
  309. {subliminal-2.4.0 → subliminal-2.5.0}/tests/refiners/test_metadata.py +0 -0
  310. {subliminal-2.4.0 → subliminal-2.5.0}/tests/refiners/test_omdb.py +0 -0
  311. {subliminal-2.4.0 → subliminal-2.5.0}/tests/refiners/test_tvdb.py +0 -0
  312. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_cache.py +0 -0
  313. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_extensions.py +0 -0
  314. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_matches.py +0 -0
  315. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_provider.py +0 -0
  316. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_score.py +0 -0
  317. {subliminal-2.4.0 → subliminal-2.5.0}/tests/test_utils.py +0 -0
  318. {subliminal-2.4.0 → subliminal-2.5.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  repos:
2
2
  - repo: https://github.com/pre-commit/pre-commit-hooks
3
- rev: v5.0.0
3
+ rev: v6.0.0
4
4
  hooks:
5
5
  - id: check-toml
6
6
  - id: check-yaml
@@ -8,12 +8,12 @@ repos:
8
8
  - id: end-of-file-fixer
9
9
 
10
10
  - repo: https://github.com/crate-ci/typos
11
- rev: v1.33.1
11
+ rev: v1.40.0
12
12
  hooks:
13
13
  - id: typos
14
14
 
15
15
  - repo: https://github.com/astral-sh/ruff-pre-commit
16
- rev: v0.12.1
16
+ rev: v0.14.9
17
17
  hooks:
18
18
  - id: ruff
19
19
  args: [--fix, --unsafe-fixes]
@@ -33,7 +33,7 @@ repos:
33
33
  require_serial: true
34
34
 
35
35
  - repo: https://github.com/woodruffw/zizmor-pre-commit
36
- rev: v1.10.0
36
+ rev: v1.18.0
37
37
  hooks:
38
38
  - id: zizmor
39
39
 
@@ -2,6 +2,33 @@
2
2
 
3
3
  .. towncrier release notes start
4
4
 
5
+ 2.5.0 (2025-12-14)
6
+ ^^^^^^^^^^^^^^^^^^
7
+
8
+ Provider Changes
9
+ ----------------
10
+
11
+ - OpenSubtitlesCom: add a max_result_pages parameter to limit the number of result pages to speed up the search (`#1321 <https://github.com/Diaoul/subliminal/issues/1321>`__)
12
+
13
+
14
+ Documentation
15
+ -------------
16
+
17
+ - fix ``generate_default_config`` for ``click==8.3`` and pin to ``click<8.3`` until another regression is fixed. (`#1318 <https://github.com/Diaoul/subliminal/issues/1318>`__)
18
+
19
+
20
+ Misc
21
+ ----
22
+
23
+ - add support for python 3.14 (`#1317 <https://github.com/Diaoul/subliminal/issues/1317>`__)
24
+ - Fix test with click 8.3.0 running in parallel (`#1320 <https://github.com/Diaoul/subliminal/issues/1320>`__)
25
+ - fix test on big-endian system (`#1323 <https://github.com/Diaoul/subliminal/issues/1323>`__)
26
+ - compatibility with stevedore 5.6.0 (`#1325 <https://github.com/Diaoul/subliminal/issues/1325>`__)
27
+ - fix some tests to pass in a dirty environment (`#1326 <https://github.com/Diaoul/subliminal/issues/1326>`__)
28
+ - update pre-commit hook versions (`#1329 <https://github.com/Diaoul/subliminal/issues/1329>`__)
29
+ - Fix some test so they are more reliable and stable (`#1331 <https://github.com/Diaoul/subliminal/issues/1331>`__)
30
+
31
+
5
32
  2.4.0 (2025-09-03)
6
33
  ^^^^^^^^^^^^^^^^^^
7
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: subliminal
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Subtitles, faster than your thoughts
5
5
  Project-URL: homepage, https://github.com/Diaoul/subliminal
6
6
  Project-URL: repository, https://github.com/Diaoul/subliminal
@@ -21,14 +21,15 @@ Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
22
  Classifier: Programming Language :: Python :: 3.12
23
23
  Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
24
25
  Classifier: Topic :: Multimedia :: Video
25
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
27
  Requires-Python: >=3.9
27
28
  Requires-Dist: babelfish>=0.6.1
28
29
  Requires-Dist: beautifulsoup4>=4.4.0
29
30
  Requires-Dist: chardet>=5.0
31
+ Requires-Dist: click
30
32
  Requires-Dist: click-option-group>=0.5.6
31
- Requires-Dist: click>=8.0
32
33
  Requires-Dist: defusedxml>=0.7.1
33
34
  Requires-Dist: dogpile-cache>=1.0
34
35
  Requires-Dist: guessit>=3.0.0
@@ -24,6 +24,10 @@ CLI arguments (like ``path``) cannot be specified in the configuration file.
24
24
 
25
25
  Options that can be used multiple times (like ``--language``) need to be defined as arrays.
26
26
 
27
+ ``hearing_impaired`` and ``foreign_only`` can take three values: "true", "false" or "[]".
28
+ They mean that hearing impaired (or foreign-only) subtitles will be, respectively,
29
+ ranked first, ranked last or not given any particular rank, for subtitles with identical scores.
30
+
27
31
 
28
32
  Example
29
33
  -------
@@ -80,7 +80,7 @@ description = "Run the tests suite"
80
80
  features = ["tests"]
81
81
 
82
82
  [[envs.tests.matrix]]
83
- python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
83
+ python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
84
84
 
85
85
  [envs.tests.env-vars]
86
86
  COVERAGE_PROCESS_START = "pyproject.toml"
@@ -29,6 +29,7 @@ classifiers = [
29
29
  "Programming Language :: Python :: 3.11",
30
30
  "Programming Language :: Python :: 3.12",
31
31
  "Programming Language :: Python :: 3.13",
32
+ "Programming Language :: Python :: 3.14",
32
33
  "Topic :: Software Development :: Libraries :: Python Modules",
33
34
  "Topic :: Multimedia :: Video",
34
35
  ]
@@ -38,7 +39,7 @@ dependencies = [
38
39
  "beautifulsoup4>=4.4.0",
39
40
  "defusedxml>=0.7.1",
40
41
  "chardet>=5.0",
41
- "click>=8.0",
42
+ "click",
42
43
  "click-option-group>=0.5.6",
43
44
  "dogpile.cache>=1.0",
44
45
  "guessit>=3.0.0",
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ from typing import Any
6
7
 
7
8
  import click
8
9
  import tomlkit
@@ -13,6 +14,61 @@ from .cli import subliminal
13
14
  logger = logging.getLogger(__name__)
14
15
 
15
16
 
17
+ def _validate(name: str, value: Any) -> str | None:
18
+ """Validate the value to be sure it is a valid TOML value.
19
+
20
+ E.g., Sentinel.UNSET was introduced in click 8.3 to be used as default value
21
+ and it is not convertible to valid TOML.
22
+ """
23
+ # Create a stub table to try importing the key and value
24
+ stub = tomlkit.table()
25
+ try:
26
+ stub.add(name, value)
27
+ except (ValueError, tomlkit.exceptions.ConvertError):
28
+ return None
29
+
30
+ return stub.as_string().strip('\n')
31
+
32
+
33
+ def _add_value_to_table(
34
+ opt: click.Option,
35
+ table: tomlkit.items.Table,
36
+ *,
37
+ name: str | None = None,
38
+ commented: bool = True,
39
+ ) -> str | None:
40
+ """Add a value to a TOML table."""
41
+ if opt.name is None: # pragma: no cover
42
+ return None
43
+ # Override option name
44
+ opt_name = name if name is not None else opt.name
45
+
46
+ # Add the description of the option as a comment above
47
+ if opt.help:
48
+ table.add(tomlkit.comment(opt.help.capitalize()))
49
+
50
+ # Add the option name with default value
51
+ if opt.default is None:
52
+ # None value is not defined in TOML, it needs to be a comment
53
+ table.add(tomlkit.comment(f'{opt_name} = '))
54
+
55
+ elif (validated := _validate(opt_name, opt.default)) is not None:
56
+ # The default value is valid TOML
57
+ if commented:
58
+ # Add the string to the final table as a comment
59
+ table.add(tomlkit.comment(validated))
60
+ else:
61
+ # Add the key and valid value to the table
62
+ table.add(opt_name, opt.default)
63
+
64
+ else:
65
+ # The default value is not valid TOML, add a comment
66
+ table.add(tomlkit.comment(f'{opt_name} = '))
67
+
68
+ # Return the key to keep track of duplicates
69
+ return opt_name
70
+
71
+
16
72
  def generate_default_config(*, compact: bool = True, commented: bool = True) -> str:
17
73
  """Generate a default configuration file.
18
74
 
@@ -20,31 +76,6 @@ def generate_default_config(*, compact: bool = True, commented: bool = True) ->
20
76
  :param commented: if True, all the options are commented out.
21
77
  :return: the default configuration as a string.
22
78
  """
23
-
24
- def add_value_to_table(opt: click.Option, table: tomlkit.items.Table, *, name: str | None = None) -> str | None:
25
- """Add a value to a TOML table."""
26
- if opt.name is None: # pragma: no cover
27
- return None
28
- # Override option name
29
- opt_name = name if name is not None else opt.name
30
-
31
- table.add(tomlkit.comment((opt.help or opt_name).capitalize()))
32
- # table.add(tomlkit.comment(f'{opt_name} = {opt.default}'))
33
- if opt.default is not None:
34
- if not commented:
35
- table.add(opt_name, opt.default)
36
- else:
37
- # Generate the entry in a dumb table
38
- dumb = tomlkit.table()
39
- dumb.add(opt_name, opt.default)
40
- # Add the string to the final table as a comment
41
- table.add(tomlkit.comment(dumb.as_string().strip('\n')))
42
- else:
43
- table.add(tomlkit.comment(f'{opt_name} = '))
44
-
45
- # Return the key to keep track of duplicates
46
- return opt_name
47
-
48
79
  # Create TOML document
49
80
  doc = tomlkit.document()
50
81
  doc.add(tomlkit.comment('Subliminal default configuration file'))
@@ -62,7 +93,7 @@ def generate_default_config(*, compact: bool = True, commented: bool = True) ->
62
93
  if opt.name in ['version', 'config']:
63
94
  continue
64
95
  # Add key=value to table
65
- add_value_to_table(opt, default)
96
+ _add_value_to_table(opt, default, commented=commented)
66
97
  if not compact: # pragma: no cover
67
98
  default.add(tomlkit.nl())
68
99
  # Adding the table to the document
@@ -85,7 +116,7 @@ def generate_default_config(*, compact: bool = True, commented: bool = True) ->
85
116
  # Duplicated option
86
117
  continue
87
118
  # Add key=value to table
88
- opt_name = add_value_to_table(opt, com_table)
119
+ opt_name = _add_value_to_table(opt, com_table, commented=commented)
89
120
  if opt_name is not None:
90
121
  existing_options.add(opt_name)
91
122
  if not compact: # pragma: no cover
@@ -114,7 +145,7 @@ def generate_default_config(*, compact: bool = True, commented: bool = True) ->
114
145
  continue
115
146
 
116
147
  # Add key=value to table
117
- add_value_to_table(opt, provider_table, name=opt_name)
148
+ _add_value_to_table(opt, provider_table, name=opt_name, commented=commented)
118
149
  if not compact: # pragma: no cover
119
150
  provider_table.add(tomlkit.nl())
120
151
 
@@ -131,12 +131,12 @@ class ProviderPool:
131
131
 
132
132
  """
133
133
  # check video validity
134
- if not provider_manager[provider].plugin.check(video):
134
+ if not provider_manager[provider].plugin.check(video): # type: ignore[attr-defined]
135
135
  logger.info('Skipping provider %r: not a valid video', provider)
136
136
  return []
137
137
 
138
138
  # check supported languages
139
- provider_languages = provider_manager[provider].plugin.check_languages(languages)
139
+ provider_languages = provider_manager[provider].plugin.check_languages(languages) # type: ignore[attr-defined]
140
140
  if not provider_languages:
141
141
  logger.info('Skipping provider %r: no language to search for', provider)
142
142
  return []
@@ -2,15 +2,18 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import inspect
5
6
  import re
6
7
  from importlib.metadata import EntryPoint
7
8
  from typing import TYPE_CHECKING, Any
8
9
 
9
- from stevedore import ExtensionManager # type: ignore[import-untyped]
10
+ from stevedore import ExtensionManager
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from collections.abc import Sequence
13
14
 
15
+ from stevedore.extension import Extension
16
+
14
17
 
15
18
  class RegistrableExtensionManager(ExtensionManager):
16
19
  """:class:`stevedore.extension.ExtensionManager` with support for registration.
@@ -29,16 +32,21 @@ class RegistrableExtensionManager(ExtensionManager):
29
32
 
30
33
  """
31
34
 
35
+ #: Registered extensions with entry point syntax
32
36
  registered_extensions: list[str]
37
+
38
+ #: Internal extensions with entry point syntax
33
39
  internal_extensions: list[str]
34
40
 
41
+ _old_api: bool
42
+
35
43
  def __init__(self, namespace: str, internal_extensions: Sequence[str], **kwargs: Any) -> None:
36
- #: Registered extensions with entry point syntax
37
44
  self.registered_extensions = []
38
-
39
- #: Internal extensions with entry point syntax
40
45
  self.internal_extensions = list(internal_extensions)
41
46
 
47
+ # 'verify_requirements' argument was remove in stevedore version 5.6.0
48
+ self._old_api = 'verify_requirements' in inspect.signature(self._load_one_plugin).parameters
49
+
42
50
  super().__init__(namespace, **kwargs)
43
51
 
44
52
  def list_entry_points(self) -> list[EntryPoint]:
@@ -60,6 +68,25 @@ class RegistrableExtensionManager(ExtensionManager):
60
68
 
61
69
  return eps
62
70
 
71
+ def __compat_load_one_plugin(self, ep: EntryPoint) -> Extension | None: # pragma: no cover
72
+ if self._old_api:
73
+ # The 'verify_requirements' argument is needed
74
+ return self._load_one_plugin( # type: ignore[call-arg]
75
+ ep,
76
+ invoke_on_load=False,
77
+ invoke_args=(), # type: ignore[arg-type]
78
+ invoke_kwds={},
79
+ verify_requirements=False,
80
+ )
81
+
82
+ # The 'verify_requirements' argument is forbidden
83
+ return self._load_one_plugin( # type: ignore[call-arg]
84
+ ep,
85
+ invoke_on_load=False,
86
+ invoke_args=(), # type: ignore[arg-type]
87
+ invoke_kwds={},
88
+ )
89
+
63
90
  def register(self, entry_point: str) -> None:
64
91
  """Register an extension.
65
92
 
@@ -76,13 +103,10 @@ class RegistrableExtensionManager(ExtensionManager):
76
103
  msg = 'An extension with the same name already exist'
77
104
  raise ValueError(msg)
78
105
 
79
- ext = self._load_one_plugin(
80
- ep,
81
- invoke_on_load=False,
82
- invoke_args=(),
83
- invoke_kwds={},
84
- verify_requirements=False,
85
- )
106
+ ext = self.__compat_load_one_plugin(ep)
107
+ if ext is None: # pragma: no cover
108
+ return
109
+
86
110
  self.extensions.append(ext)
87
111
  if self._extensions_by_name is not None: # pragma: no branch
88
112
  self._extensions_by_name[ext.name] = ext
@@ -171,8 +171,9 @@ class OpenSubtitlesSubtitle(Subtitle):
171
171
  class OpenSubtitlesProvider(Provider):
172
172
  """OpenSubtitles Provider.
173
173
 
174
- :param str username: username.
175
- :param str password: password.
174
+ :param str username: username
175
+ :param str password: password
176
+ :param int timeout: timeout in seconds. Default to 10.
176
177
 
177
178
  """
178
179
 
@@ -195,6 +196,7 @@ class OpenSubtitlesProvider(Provider):
195
196
  username: str | None = None,
196
197
  password: str | None = None,
197
198
  *,
199
+ max_result_pages: int = 0,
198
200
  timeout: int = 10,
199
201
  ) -> None:
200
202
  transport = TimeoutSafeTransport(timeout=timeout, user_agent='VLSub')
@@ -279,7 +281,7 @@ class OpenSubtitlesProvider(Provider):
279
281
  language = Language.fromopensubtitles(subtitle_item['SubLanguageID'])
280
282
  hearing_impaired = bool(int(subtitle_item['SubHearingImpaired']))
281
283
  page_link = subtitle_item['SubtitlesLink']
282
- subtitle_id = int(subtitle_item['IDSubtitleFile'])
284
+ subtitle_id = str(int(subtitle_item['IDSubtitleFile']))
283
285
  matched_by = subtitle_item['MatchedBy']
284
286
  movie_kind = subtitle_item['MovieKind']
285
287
  moviehash = subtitle_item['MovieHash']
@@ -370,8 +370,13 @@ def requires_auth(func: C) -> C:
370
370
  class OpenSubtitlesComProvider(Provider):
371
371
  """OpenSubtitles.com Provider.
372
372
 
373
- :param str username: username.
374
- :param str password: password.
373
+ :param str username: username
374
+ :param str password: password
375
+ :param str apikey: private API key
376
+ :param int max_result_pages: maximum number of result pages to process,
377
+ set to non-zero to search faster, although the correct match can be
378
+ missing. Default to 0 (unlimited).
379
+ :param int timeout: timeout in seconds. Default to 20.
375
380
 
376
381
  """
377
382
 
@@ -385,6 +390,7 @@ class OpenSubtitlesComProvider(Provider):
385
390
  username: str | None
386
391
  password: str | None
387
392
  apikey: str
393
+ max_result_pages: int
388
394
  timeout: int
389
395
  token_expires_at: datetime | None
390
396
  session: Session | None
@@ -395,6 +401,7 @@ class OpenSubtitlesComProvider(Provider):
395
401
  password: str | None = None,
396
402
  *,
397
403
  apikey: str | None = None,
404
+ max_result_pages: int = 0,
398
405
  timeout: int = 20,
399
406
  ) -> None:
400
407
  if any((username, password)) and not all((username, password)):
@@ -404,6 +411,7 @@ class OpenSubtitlesComProvider(Provider):
404
411
  self.username = username
405
412
  self.password = password
406
413
  self.apikey = apikey or OPENSUBTITLESCOM_API_KEY
414
+ self.max_result_pages = max_result_pages
407
415
  self.timeout = timeout
408
416
  self.token_expires_at = None
409
417
  self.session = None
@@ -588,19 +596,25 @@ class OpenSubtitlesComProvider(Provider):
588
596
  return r.json() # type: ignore[no-any-return]
589
597
 
590
598
  def _search(self, *, page: int = 1, **params: Any) -> list[dict[str, Any]]:
599
+ # Extended params with page
600
+ ext_params = {'page': page, **params}
601
+
591
602
  # query the server
592
- logger.info('Searching subtitles %r', params)
603
+ logger.info('Searching subtitles %r', ext_params)
593
604
 
594
605
  # GET request and add page information
595
- response = self.api_get('subtitles', {'page': page, **params})
606
+ response = self.api_get('subtitles', ext_params)
596
607
 
597
608
  if not response or not response['data']:
598
609
  return []
599
610
 
600
611
  ret = response['data']
601
612
 
613
+ # check that the maximum number of pages has not been exceeded
614
+ allow_more_pages = self.max_result_pages <= 0 or page < self.max_result_pages
615
+
602
616
  # retrieve other pages maybe
603
- if 'total_pages' in response and page < response['total_pages']:
617
+ if allow_more_pages and 'total_pages' in response and page < response['total_pages']:
604
618
  # missing pages
605
619
  ret.extend(self._search(page=page + 1, **params))
606
620
 
@@ -642,7 +656,8 @@ class OpenSubtitlesComProvider(Provider):
642
656
  if query:
643
657
  criterion.update({'query': query.replace("'", '')})
644
658
 
645
- if season and episode:
659
+ # Check for None to allow season=0 and episode=0
660
+ if season is not None and episode is not None:
646
661
  criterion.update({'season_number': season, 'episode_number': episode})
647
662
  if year:
648
663
  criterion.update({'year': year})
@@ -312,7 +312,7 @@ if WITH_SYMPY: # pragma: no cover
312
312
  """
313
313
  hash, title, year, country, release_group = symbols('hash title year country release_group') # noqa: A001
314
314
  streaming_service, fps, source, audio_codec = symbols('streaming_service fps source audio_codec')
315
- resolution, video_codec, hearing_impaired = symbols('resolution video_codec hearing_impaired')
315
+ resolution, video_codec = symbols('resolution video_codec')
316
316
 
317
317
  equations = [
318
318
  # hash is best