subliminal 2.3.2__tar.gz → 2.4.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 (317) hide show
  1. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/publish-release.yaml +2 -30
  2. subliminal-2.4.0/.github/zizmor.yml +13 -0
  3. {subliminal-2.3.2 → subliminal-2.4.0}/.pre-commit-config.yaml +4 -4
  4. {subliminal-2.3.2 → subliminal-2.4.0}/HISTORY.rst +27 -0
  5. {subliminal-2.3.2 → subliminal-2.4.0}/PKG-INFO +9 -2
  6. {subliminal-2.3.2 → subliminal-2.4.0}/README.rst +8 -0
  7. {subliminal-2.3.2 → subliminal-2.4.0}/docs/conf.py +1 -1
  8. {subliminal-2.3.2 → subliminal-2.4.0}/docs/user/configuration_file.rst +1 -1
  9. {subliminal-2.3.2 → subliminal-2.4.0}/docs/user/usage.rst +2 -2
  10. {subliminal-2.3.2 → subliminal-2.4.0}/pyproject.toml +2 -3
  11. subliminal-2.4.0/src/subliminal/cli/__init__.py +6 -0
  12. subliminal-2.4.0/src/subliminal/cli/cli.py +241 -0
  13. subliminal-2.4.0/src/subliminal/cli/commands/__init__.py +5 -0
  14. subliminal-2.4.0/src/subliminal/cli/commands/_format.py +63 -0
  15. subliminal-2.4.0/src/subliminal/cli/commands/download_best.py +632 -0
  16. subliminal-2.4.0/src/subliminal/cli/generate_config.py +131 -0
  17. subliminal-2.4.0/src/subliminal/cli/helpers.py +230 -0
  18. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/subtitulamos.py +1 -0
  19. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/refiners/omdb.py +23 -20
  20. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/refiners/tvdb.py +8 -3
  21. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/score.py +47 -52
  22. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/utils.py +11 -45
  23. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/video.py +2 -2
  24. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_download_subtitle.yaml +5 -5
  25. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_list_subtitles_episode.yaml +4 -4
  26. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_list_subtitles_movie.yaml +4 -4
  27. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_list_subtitles_movie_no_hash.yaml +4 -4
  28. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_login_bad_password.yaml +1 -1
  29. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_login_vip_bad_password.yaml +1 -1
  30. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_login_vip_login.yaml +1 -1
  31. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_no_operation.yaml +4 -4
  32. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_query_hash_size.yaml +4 -4
  33. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_query_imdb_id.yaml +4 -4
  34. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_query_not_enough_information.yaml +3 -3
  35. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_query_query_episode.yaml +4 -4
  36. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_query_query_movie.yaml +4 -4
  37. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_query_query_season_episode.yaml +4 -4
  38. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitles/test_query_wrong_hash_wrong_size.yaml +4 -4
  39. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_download_subtitle.yaml +7 -7
  40. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_episode.yaml +2 -2
  41. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_movie.yaml +4 -4
  42. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_movie_no_hash.yaml +1 -1
  43. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_login.yaml +1 -1
  44. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_login_bad_password.yaml +1 -1
  45. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_logout.yaml +1 -1
  46. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_query_hash_size.yaml +1 -1
  47. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_query_imdb_id.yaml +1 -1
  48. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_query_query_episode.yaml +2 -2
  49. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_query_query_movie.yaml +1 -1
  50. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_query_query_season_episode.yaml +2 -2
  51. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_query_tag_movie.yaml +1 -1
  52. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_tag_match.yaml +3 -3
  53. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_user_infos.yaml +2 -2
  54. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/providers/test_list_subtitles_providers_download.yaml +8 -8
  55. subliminal-2.4.0/tests/cli/test_cli.py +32 -0
  56. subliminal-2.3.2/tests/test_cli.py → subliminal-2.4.0/tests/cli/test_download.py +3 -66
  57. subliminal-2.4.0/tests/cli/test_generate_config.py +33 -0
  58. subliminal-2.4.0/tests/cli/test_helpers.py +95 -0
  59. {subliminal-2.3.2 → subliminal-2.4.0}/tests/conftest.py +9 -2
  60. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_archives.py +2 -3
  61. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_core.py +0 -1
  62. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_utils.py +20 -66
  63. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_video.py +15 -0
  64. {subliminal-2.3.2 → subliminal-2.4.0}/tox.ini +0 -8
  65. subliminal-2.3.2/.github/zizmor.yml +0 -4
  66. subliminal-2.3.2/scripts/generate-gh-release-notes.py +0 -86
  67. subliminal-2.3.2/src/subliminal/cli.py +0 -1169
  68. {subliminal-2.3.2 → subliminal-2.4.0}/.dockerignore +0 -0
  69. {subliminal-2.3.2 → subliminal-2.4.0}/.github/actions/prepare-tests/action.yaml +0 -0
  70. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/CI.yaml +0 -0
  71. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/coverage.yaml +0 -0
  72. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/docker-build.yaml +0 -0
  73. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/prepare-release-pr.yaml +0 -0
  74. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/publish.yaml +0 -0
  75. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/scheduled-tests.yaml +0 -0
  76. {subliminal-2.3.2 → subliminal-2.4.0}/.github/workflows/tag-release.yaml +0 -0
  77. {subliminal-2.3.2 → subliminal-2.4.0}/.gitignore +0 -0
  78. {subliminal-2.3.2 → subliminal-2.4.0}/.python-version-default +0 -0
  79. {subliminal-2.3.2 → subliminal-2.4.0}/.readthedocs.yaml +0 -0
  80. {subliminal-2.3.2 → subliminal-2.4.0}/CONTRIBUTING.md +0 -0
  81. {subliminal-2.3.2 → subliminal-2.4.0}/Dockerfile +0 -0
  82. {subliminal-2.3.2 → subliminal-2.4.0}/LICENSE +0 -0
  83. {subliminal-2.3.2 → subliminal-2.4.0}/MANIFEST.in +0 -0
  84. {subliminal-2.3.2 → subliminal-2.4.0}/RELEASING.md +0 -0
  85. {subliminal-2.3.2 → subliminal-2.4.0}/changelog.d/.gitignore +0 -0
  86. {subliminal-2.3.2 → subliminal-2.4.0}/docs/Makefile +0 -0
  87. {subliminal-2.3.2 → subliminal-2.4.0}/docs/_intersphinx/README.md +0 -0
  88. {subliminal-2.3.2 → subliminal-2.4.0}/docs/_intersphinx/importlib_metadata_objects.inv +0 -0
  89. {subliminal-2.3.2 → subliminal-2.4.0}/docs/_intersphinx/importlib_metadata_objects.txt +0 -0
  90. {subliminal-2.3.2 → subliminal-2.4.0}/docs/_static/.gitignore +0 -0
  91. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/cache.rst +0 -0
  92. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/cli.rst +0 -0
  93. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/core.rst +0 -0
  94. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/exceptions.rst +0 -0
  95. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/extensions.rst +0 -0
  96. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/providers.rst +0 -0
  97. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/refiners.rst +0 -0
  98. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/score.rst +0 -0
  99. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/subtitle.rst +0 -0
  100. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/utils.rst +0 -0
  101. {subliminal-2.3.2 → subliminal-2.4.0}/docs/api/video.rst +0 -0
  102. {subliminal-2.3.2 → subliminal-2.4.0}/docs/cassettes/test_usage.yaml +0 -0
  103. {subliminal-2.3.2 → subliminal-2.4.0}/docs/config.toml +0 -0
  104. {subliminal-2.3.2 → subliminal-2.4.0}/docs/conftest.py +0 -0
  105. {subliminal-2.3.2 → subliminal-2.4.0}/docs/index.rst +0 -0
  106. {subliminal-2.3.2 → subliminal-2.4.0}/docs/make.bat +0 -0
  107. {subliminal-2.3.2 → subliminal-2.4.0}/docs/user/changelog.rst +0 -0
  108. {subliminal-2.3.2 → subliminal-2.4.0}/docs/user/cli.rst +0 -0
  109. {subliminal-2.3.2 → subliminal-2.4.0}/docs/user/how_it_works.rst +0 -0
  110. {subliminal-2.3.2 → subliminal-2.4.0}/docs/user/installation.rst +0 -0
  111. {subliminal-2.3.2 → subliminal-2.4.0}/docs/user/provider_guide.rst +0 -0
  112. {subliminal-2.3.2 → subliminal-2.4.0}/hatch.toml +0 -0
  113. {subliminal-2.3.2 → subliminal-2.4.0}/scripts/.gitignore +0 -0
  114. {subliminal-2.3.2 → subliminal-2.4.0}/scripts/prepare-release-pr.py +0 -0
  115. {subliminal-2.3.2 → subliminal-2.4.0}/scripts/prepare_tests.py +0 -0
  116. {subliminal-2.3.2 → subliminal-2.4.0}/scripts/release.py +0 -0
  117. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/__init__.py +0 -0
  118. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/__main__.py +0 -0
  119. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/archives.py +0 -0
  120. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/cache.py +0 -0
  121. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/converters/__init__.py +0 -0
  122. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/converters/addic7ed.py +0 -0
  123. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/converters/opensubtitles.py +0 -0
  124. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/converters/opensubtitlescom.py +0 -0
  125. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/converters/subtitulamos.py +0 -0
  126. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/converters/tvsubtitles.py +0 -0
  127. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/core.py +0 -0
  128. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/exceptions.py +0 -0
  129. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/extensions.py +0 -0
  130. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/matches.py +0 -0
  131. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/__init__.py +0 -0
  132. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/addic7ed.py +0 -0
  133. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/bsplayer.py +0 -0
  134. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/gestdown.py +0 -0
  135. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/mock.py +0 -0
  136. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/napiprojekt.py +0 -0
  137. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/opensubtitles.py +0 -0
  138. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/opensubtitlescom.py +0 -0
  139. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/podnapisi.py +0 -0
  140. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/providers/tvsubtitles.py +0 -0
  141. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/py.typed +0 -0
  142. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/refiners/__init__.py +0 -0
  143. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/refiners/hash.py +0 -0
  144. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/refiners/metadata.py +0 -0
  145. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/refiners/tmdb.py +0 -0
  146. {subliminal-2.3.2 → subliminal-2.4.0}/src/subliminal/subtitle.py +0 -0
  147. {subliminal-2.3.2 → subliminal-2.4.0}/tests/__init__.py +0 -0
  148. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_download_subtitle.yaml +0 -0
  149. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_get_show_id.yaml +0 -0
  150. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_get_show_id_country.yaml +0 -0
  151. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_get_show_id_quote_dots_mixed_case.yaml +0 -0
  152. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_get_show_id_with_comma.yaml +0 -0
  153. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_get_show_id_year.yaml +0 -0
  154. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_get_show_ids.yaml +0 -0
  155. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_list_subtitles.yaml +0 -0
  156. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_list_subtitles_episode_alternative_series.yaml +0 -0
  157. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_list_subtitles_show_with_asterisk.yaml +0 -0
  158. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query.yaml +0 -0
  159. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query_no_year.yaml +0 -0
  160. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query_parsing.yaml +0 -0
  161. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query_parsing_colon.yaml +0 -0
  162. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query_parsing_dash.yaml +0 -0
  163. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query_parsing_quote_dots_mixed_case.yaml +0 -0
  164. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query_wrong_series.yaml +0 -0
  165. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/addic7ed/test_query_year.yaml +0 -0
  166. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/bsplayer/test_download_subtitle.yaml +0 -0
  167. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/bsplayer/test_list_subtitles_hash.yaml +0 -0
  168. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/bsplayer/test_login.yaml +0 -0
  169. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/bsplayer/test_logout.yaml +0 -0
  170. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/bsplayer/test_query_hash_size.yaml +0 -0
  171. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/cli/test_cli_download.yaml +0 -0
  172. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_download_subtitle.yaml +0 -0
  173. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_download_with_bom.yaml +0 -0
  174. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_get_title_and_show_id_alternative_name.yaml +0 -0
  175. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_get_title_and_show_id_no_year.yaml +0 -0
  176. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_get_title_and_show_id_only_title.yaml +0 -0
  177. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_get_title_and_show_id_with_tvdb_id.yaml +0 -0
  178. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_list_subtitles.yaml +0 -0
  179. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_list_subtitles_episode_alternative_series.yaml +0 -0
  180. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query.yaml +0 -0
  181. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query_all_series.yaml +0 -0
  182. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query_no_language.yaml +0 -0
  183. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query_parsing.yaml +0 -0
  184. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query_parsing_colon.yaml +0 -0
  185. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query_parsing_dash.yaml +0 -0
  186. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query_parsing_quote_dots_mixed_case.yaml +0 -0
  187. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_query_wrong_series.yaml +0 -0
  188. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_search_show_id.yaml +0 -0
  189. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_search_show_id_error.yaml +0 -0
  190. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_search_show_id_incomplete.yaml +0 -0
  191. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_search_show_id_no_year.yaml +0 -0
  192. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_search_show_id_quote.yaml +0 -0
  193. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_search_show_series_tvdb_id.yaml +0 -0
  194. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/gestdown/test_show_with_asterisk.yaml +0 -0
  195. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/napiprojekt/test_download_subtitles.yaml +0 -0
  196. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/napiprojekt/test_list_subtitles.yaml +0 -0
  197. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/napiprojekt/test_query_microdvd.yaml +0 -0
  198. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/napiprojekt/test_query_srt.yaml +0 -0
  199. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/napiprojekt/test_query_srt_reencode.yaml +0 -0
  200. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/napiprojekt/test_query_wrong_hash.yaml +0 -0
  201. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_get_id.yaml +0 -0
  202. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_get_title.yaml +0 -0
  203. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_get_wrong_id.yaml +0 -0
  204. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_get_wrong_title.yaml +0 -0
  205. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_refine_episode.yaml +0 -0
  206. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_refine_episode_original_series.yaml +0 -0
  207. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_refine_episode_with_country.yaml +0 -0
  208. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
  209. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_refine_episode_year.yaml +0 -0
  210. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_refine_movie.yaml +0 -0
  211. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_refine_movie_guess_alternative_title.yaml +0 -0
  212. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_search.yaml +0 -0
  213. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_search_page.yaml +0 -0
  214. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_search_type.yaml +0 -0
  215. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_search_wrong_title.yaml +0 -0
  216. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/omdb/test_search_year.yaml +0 -0
  217. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/opensubtitlescom/test_query_wrong_hash_wrong_size.yaml +0 -0
  218. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/podnapisi/test_download_subtitle.yaml +0 -0
  219. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/podnapisi/test_download_subtitles_with_title_unicode.yaml +0 -0
  220. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/podnapisi/test_list_subtitles_episode.yaml +0 -0
  221. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/podnapisi/test_list_subtitles_episode_alternative_series.yaml +0 -0
  222. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/podnapisi/test_list_subtitles_movie.yaml +0 -0
  223. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/podnapisi/test_query_episode.yaml +0 -0
  224. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/podnapisi/test_query_movie.yaml +0 -0
  225. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/providers/test_download_best_subtitles.yaml +0 -0
  226. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/providers/test_download_best_subtitles_min_score.yaml +0 -0
  227. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/providers/test_download_best_subtitles_only_one.yaml +0 -0
  228. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_download_subtitle.yaml +0 -0
  229. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_download_subtitle_first_episode.yaml +0 -0
  230. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_download_subtitle_foo.yaml +0 -0
  231. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_download_subtitle_last_season.yaml +0 -0
  232. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_download_subtitle_year.yaml +0 -0
  233. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_episode.yaml +0 -0
  234. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_language.yaml +0 -0
  235. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_season.yaml +0 -0
  236. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_series.yaml +0 -0
  237. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/subtitulamos/test_list_subtitles_with_spanish_non_mx_search.yaml +0 -0
  238. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_get_movie_id.yaml +0 -0
  239. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_get_movie_id_failed.yaml +0 -0
  240. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_get_series_id.yaml +0 -0
  241. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_get_series_wrong_id.yaml +0 -0
  242. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_refine_episode.yaml +0 -0
  243. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_refine_episode_original_series.yaml +0 -0
  244. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_refine_episode_with_country.yaml +0 -0
  245. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
  246. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_refine_episode_year.yaml +0 -0
  247. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_refine_movie.yaml +0 -0
  248. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_refine_movie_guess_alternative_title.yaml +0 -0
  249. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_search_movie.yaml +0 -0
  250. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_search_movie_page.yaml +0 -0
  251. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_search_movie_wrong_title.yaml +0 -0
  252. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_search_movie_year.yaml +0 -0
  253. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_search_series.yaml +0 -0
  254. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tmdb/test_search_series_wrong_name.yaml +0 -0
  255. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_get_series.yaml +0 -0
  256. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_get_series_actors.yaml +0 -0
  257. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_get_series_actors_wrong_id.yaml +0 -0
  258. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_get_series_wrong_id.yaml +0 -0
  259. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_login.yaml +0 -0
  260. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_login_error.yaml +0 -0
  261. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_query_series_episodes.yaml +0 -0
  262. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_query_series_episodes_wrong_season.yaml +0 -0
  263. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine.yaml +0 -0
  264. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_ambiguous.yaml +0 -0
  265. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_ambiguous_2.yaml +0 -0
  266. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_episode_alternative_series.yaml +0 -0
  267. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_episode_no_year.yaml +0 -0
  268. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_episode_partial.yaml +0 -0
  269. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_episode_with_comma.yaml +0 -0
  270. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_episode_with_country.yaml +0 -0
  271. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
  272. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refine_episode_year.yaml +0 -0
  273. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_refresh_token.yaml +0 -0
  274. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_search_series.yaml +0 -0
  275. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_search_series_wrong_name.yaml +0 -0
  276. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvdb/test_token_needs_refresh.yaml +0 -0
  277. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_download_subtitle.yaml +0 -0
  278. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_download_subtitle_with_quote.yaml +0 -0
  279. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_get_episode_ids.yaml +0 -0
  280. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_get_episode_ids_wrong_season.yaml +0 -0
  281. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_list_subtitles.yaml +0 -0
  282. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_list_subtitles_episode_alternative_series.yaml +0 -0
  283. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_query.yaml +0 -0
  284. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_query_no_year.yaml +0 -0
  285. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_query_with_quote.yaml +0 -0
  286. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_query_wrong_episode.yaml +0 -0
  287. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_query_wrong_series.yaml +0 -0
  288. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id.yaml +0 -0
  289. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id_ambiguous.yaml +0 -0
  290. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id_error.yaml +0 -0
  291. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id_incomplete.yaml +0 -0
  292. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id_no_year.yaml +0 -0
  293. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id_uk.yaml +0 -0
  294. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id_us.yaml +0 -0
  295. {subliminal-2.3.2 → subliminal-2.4.0}/tests/cassettes/tvsubtitles/test_search_show_id_year_in_title.yaml +0 -0
  296. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_addic7ed.py +0 -0
  297. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_bsplayer.py +0 -0
  298. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_gestdown.py +0 -0
  299. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_napiprojekt.py +0 -0
  300. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_opensubtitles.py +0 -0
  301. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_opensubtitlescom.py +0 -0
  302. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_podnapisi.py +0 -0
  303. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_providers.py +0 -0
  304. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_subtitulamos.py +0 -0
  305. {subliminal-2.3.2 → subliminal-2.4.0}/tests/providers/test_tvsubtitles.py +0 -0
  306. {subliminal-2.3.2 → subliminal-2.4.0}/tests/refiners/test_hash.py +0 -0
  307. {subliminal-2.3.2 → subliminal-2.4.0}/tests/refiners/test_metadata.py +0 -0
  308. {subliminal-2.3.2 → subliminal-2.4.0}/tests/refiners/test_omdb.py +0 -0
  309. {subliminal-2.3.2 → subliminal-2.4.0}/tests/refiners/test_tmdb.py +0 -0
  310. {subliminal-2.3.2 → subliminal-2.4.0}/tests/refiners/test_tvdb.py +0 -0
  311. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_cache.py +0 -0
  312. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_extensions.py +0 -0
  313. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_matches.py +0 -0
  314. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_provider.py +0 -0
  315. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_provider_pool.py +0 -0
  316. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_score.py +0 -0
  317. {subliminal-2.3.2 → subliminal-2.4.0}/tests/test_subtitle.py +0 -0
@@ -35,7 +35,6 @@ jobs:
35
35
  if: github.event_name == 'push'
36
36
  with:
37
37
  persist-credentials: false
38
-
39
38
  - name: Checkout
40
39
  uses: actions/checkout@v4
41
40
  if: |
@@ -44,34 +43,13 @@ jobs:
44
43
  with:
45
44
  ref: ${{ inputs.tag-name }}
46
45
  persist-credentials: false
47
-
48
46
  - name: Build dist
49
47
  uses: hynek/build-and-inspect-python-package@v2
50
48
  id: baipp
51
-
52
- - name: Install tox
53
- run: |
54
- python -m pip install --upgrade pip
55
- pip install --upgrade tox
56
-
57
- - name: Generate Release Notes
58
- env:
59
- RELEASE_VERSION: ${{ steps.baipp.outputs.package_version }}
60
- run: |
61
- sudo apt-get install pandoc
62
- # use package_version computed in step 'baipp'
63
- tox -e generate-gh-release-notes -- "$RELEASE_VERSION" latest-release-notes.md
64
-
65
- - name: Upload release notes
66
- uses: actions/upload-artifact@v4
67
- with:
68
- name: latest-release-notes
69
- path: latest-release-notes.md
70
- include-hidden-files: false
71
- if-no-files-found: error
72
49
  outputs:
73
50
  tag: ${{ steps.baipp.outputs.package_version }}
74
51
 
52
+
75
53
  github-release:
76
54
  name: Make a GitHub Release
77
55
  needs: [build-package]
@@ -96,15 +74,9 @@ jobs:
96
74
  with:
97
75
  name: Packages
98
76
  path: dist
99
- - name: Download release notes
100
- uses: actions/download-artifact@v4
101
- with:
102
- name: latest-release-notes
103
- path: latest-release-notes.md
104
-
105
77
  - name: Publish GitHub Release
106
78
  uses: softprops/action-gh-release@v2
107
79
  with:
108
80
  files: dist/*
109
- body_path: latest-release-notes.md
81
+ generate_release_notes: true
110
82
  draft: true
@@ -0,0 +1,13 @@
1
+ rules:
2
+ dangerous-triggers:
3
+ ignore:
4
+ - coverage.yaml:3:1
5
+ unpinned-uses:
6
+ ignore:
7
+ - action.yaml
8
+ - CI.yaml
9
+ - coverage.yaml
10
+ - docker-build.yaml
11
+ - publish-release.yaml
12
+ - publish.yaml
13
+ - tag-release.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.30.3
11
+ rev: v1.33.1
12
12
  hooks:
13
13
  - id: typos
14
14
 
15
15
  - repo: https://github.com/astral-sh/ruff-pre-commit
16
- rev: v0.11.2
16
+ rev: v0.12.1
17
17
  hooks:
18
18
  - id: ruff
19
19
  args: [--fix, --unsafe-fixes]
@@ -26,14 +26,14 @@ repos:
26
26
  name: validate-pyproject
27
27
 
28
28
  - repo: https://github.com/PyCQA/doc8
29
- rev: v1.1.2
29
+ rev: v2.0.0
30
30
  hooks:
31
31
  - id: doc8
32
32
  description: This hook runs doc8 for linting docs
33
33
  require_serial: true
34
34
 
35
35
  - repo: https://github.com/woodruffw/zizmor-pre-commit
36
- rev: v1.5.2
36
+ rev: v1.10.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.4.0 (2025-09-03)
6
+ ^^^^^^^^^^^^^^^^^^
7
+
8
+ Changes
9
+ -------
10
+
11
+ - score: add fps match, remove hearing_impaired match (`#1250 <https://github.com/Diaoul/subliminal/issues/1250>`__)
12
+
13
+
14
+ CLI Changes
15
+ -----------
16
+
17
+ - cli: refactor the cli.py file into a folder (`#1299 <https://github.com/Diaoul/subliminal/issues/1299>`__)
18
+
19
+
20
+ Documentation
21
+ -------------
22
+
23
+ - doc: add a section in the README to run subliminal with the ``--debug`` flag. (`#1308 <https://github.com/Diaoul/subliminal/issues/1308>`__)
24
+
25
+
26
+ Misc
27
+ ----
28
+
29
+ - ensure Episode.series is always a string, not a list from guessit bug (`#1304 <https://github.com/Diaoul/subliminal/issues/1304>`__)
30
+
31
+
5
32
  2.3.2 (2025-05-08)
6
33
  ^^^^^^^^^^^^^^^^^^
7
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: subliminal
3
- Version: 2.3.2
3
+ Version: 2.4.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
@@ -56,7 +56,6 @@ Provides-Extra: tests
56
56
  Requires-Dist: colorama; extra == 'tests'
57
57
  Requires-Dist: coverage[toml]>=7; extra == 'tests'
58
58
  Requires-Dist: importlib-metadata>=4.6; (python_version < '3.10') and extra == 'tests'
59
- Requires-Dist: pypandoc; extra == 'tests'
60
59
  Requires-Dist: pytest-cov; extra == 'tests'
61
60
  Requires-Dist: pytest-xdist; extra == 'tests'
62
61
  Requires-Dist: pytest>=6.0; extra == 'tests'
@@ -159,6 +158,14 @@ Run subliminal in a docker container::
159
158
 
160
159
  $ docker run --rm --name subliminal -v subliminal_cache:/usr/src/cache -v /tvshows:/tvshows -it ghcr.io/diaoul/subliminal download -l en /tvshows/The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4
161
160
 
161
+ Debugging
162
+ ^^^^^^^^^
163
+
164
+ By default, subliminal output is minimal. Run with the `--debug` flag before the `download` command to get more information::
165
+
166
+ $ subliminal --debug download -l en The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4
167
+
168
+
162
169
  Installation
163
170
  ------------
164
171
  For a better isolation with your system you should use a dedicated virtualenv.
@@ -86,6 +86,14 @@ Run subliminal in a docker container::
86
86
 
87
87
  $ docker run --rm --name subliminal -v subliminal_cache:/usr/src/cache -v /tvshows:/tvshows -it ghcr.io/diaoul/subliminal download -l en /tvshows/The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4
88
88
 
89
+ Debugging
90
+ ^^^^^^^^^
91
+
92
+ By default, subliminal output is minimal. Run with the `--debug` flag before the `download` command to get more information::
93
+
94
+ $ subliminal --debug download -l en The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4
95
+
96
+
89
97
  Installation
90
98
  ------------
91
99
  For a better isolation with your system you should use a dedicated virtualenv.
@@ -304,7 +304,7 @@ intersphinx_mapping = {
304
304
  'dogpilecache': ('https://dogpilecache.sqlalchemy.org/en/latest', None),
305
305
  'stevedore': ('https://docs.openstack.org/stevedore/latest', None),
306
306
  'click': ('https://click.palletsprojects.com/en/latest/', None),
307
- 'requests': ('https://docs.python-requests.org/en/latest/', None),
307
+ 'requests': ('https://requests.readthedocs.io/en/latest/', None),
308
308
  'sympy': ('https://docs.sympy.org/latest/', None),
309
309
  'bs4': ('https://www.crummy.com/software/BeautifulSoup/bs4/doc/', None),
310
310
  'importlib.metadata': ('https://docs.python.org/3/', '_intersphinx/importlib_metadata_objects.inv'),
@@ -40,5 +40,5 @@ Default configuration
40
40
  Here is a list of all the options of the configuration file.
41
41
  Commented options need a value to be valid.
42
42
 
43
- .. program-output:: python -c "from subliminal.cli import generate_default_config; print(generate_default_config(commented=False))"
43
+ .. program-output:: python -c "from subliminal.cli.generate_config import generate_default_config; print(generate_default_config(commented=False))"
44
44
  :language: toml
@@ -146,8 +146,8 @@ And then compute a score with those matches with :func:`~subliminal.score.comput
146
146
 
147
147
  >>> for s in subtitles[video]:
148
148
  ... {s: compute_score(s, video)}
149
- {<PodnapisiSubtitle 'ZtAW' [hu]>: 789}
150
- {<PodnapisiSubtitle 'ONAW' [hu]>: 772}
149
+ {<PodnapisiSubtitle 'ZtAW' [hu]>: 941}
150
+ {<PodnapisiSubtitle 'ONAW' [hu]>: 922}
151
151
 
152
152
  Now you should have a better idea about which one you should choose.
153
153
 
@@ -72,7 +72,6 @@ tests = [
72
72
  "pytest-xdist",
73
73
  "sympy",
74
74
  "colorama", # used in scripts/
75
- "pypandoc", # used in scripts/
76
75
  "vcrpy>=5", # keep synchronized with docs dependencies
77
76
  "win32-setctime; sys_platform=='win32'",
78
77
  "importlib_metadata>=4.6; python_version<'3.10'",
@@ -275,12 +274,12 @@ showcontent = true
275
274
  [[tool.towncrier.type]]
276
275
  directory = "doc"
277
276
  name = "Documentation"
278
- showcontent = false
277
+ showcontent = true
279
278
 
280
279
  [[tool.towncrier.type]]
281
280
  directory = "misc"
282
281
  name = "Misc"
283
- showcontent = false
282
+ showcontent = true
284
283
 
285
284
 
286
285
  # https://mypy.readthedocs.io/en/stable/config_file.html
@@ -0,0 +1,6 @@
1
+ """CLI module."""
2
+
3
+ from .cli import cli
4
+ from .generate_config import generate_default_config
5
+
6
+ __all__ = ['cli', 'generate_default_config']
@@ -0,0 +1,241 @@
1
+ # ruff: noqa: FBT001
2
+ """Subliminal uses `click <https://click.palletsprojects.com>`_ to provide a :abbr:`CLI (command-line interface)`."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ import os
8
+ import pathlib
9
+ import warnings
10
+ from datetime import timedelta
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import click
15
+ from platformdirs import PlatformDirs
16
+
17
+ from subliminal import (
18
+ __version__,
19
+ region,
20
+ )
21
+
22
+ from .commands import download
23
+ from .helpers import (
24
+ MutexLock,
25
+ options_from_providers,
26
+ options_from_refiners,
27
+ providers_config,
28
+ read_configuration,
29
+ )
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ def configure(ctx: click.Context, param: click.Parameter | None, filename: str | os.PathLike) -> None:
35
+ """Update :class:`click.Context` based on a configuration file."""
36
+ config = read_configuration(filename)
37
+
38
+ ctx.obj = config['obj']
39
+ ctx.default_map = config['default_map']
40
+
41
+
42
+ dirs = PlatformDirs('subliminal')
43
+ cache_file = 'subliminal.dbm'
44
+ default_config_path = dirs.user_config_path / 'subliminal.toml'
45
+
46
+
47
+ @click.group(
48
+ context_settings={'max_content_width': 100},
49
+ epilog='Suggestions and bug reports are greatly appreciated: https://github.com/Diaoul/subliminal/',
50
+ )
51
+ @click.option(
52
+ '-c',
53
+ '--config',
54
+ type=click.Path(dir_okay=False, path_type=pathlib.Path),
55
+ default=default_config_path,
56
+ callback=configure,
57
+ show_default=True,
58
+ is_eager=True,
59
+ expose_value=False,
60
+ show_envvar=True,
61
+ help='Path to the TOML configuration file.',
62
+ )
63
+ @click.option(
64
+ '--cache-dir',
65
+ type=click.Path(writable=True, file_okay=False),
66
+ default=dirs.user_cache_dir,
67
+ show_default=True,
68
+ expose_value=True,
69
+ help='Path to the cache directory.',
70
+ )
71
+ @providers_config.option(
72
+ '--addic7ed',
73
+ type=click.STRING,
74
+ nargs=2,
75
+ metavar='USERNAME PASSWORD',
76
+ help='DEPRECATED: Addic7ed configuration.',
77
+ )
78
+ @providers_config.option(
79
+ '--opensubtitles',
80
+ type=click.STRING,
81
+ nargs=2,
82
+ metavar='USERNAME PASSWORD',
83
+ help='DEPRECATED: OpenSubtitles configuration.',
84
+ )
85
+ @providers_config.option(
86
+ '--opensubtitlescom',
87
+ type=click.STRING,
88
+ nargs=2,
89
+ metavar='USERNAME PASSWORD',
90
+ help='DEPRECATED: OpenSubtitlesCom configuration.',
91
+ )
92
+ @options_from_providers
93
+ @options_from_refiners
94
+ @click.option('--debug', is_flag=True, help='Print useful information for debugging subliminal and for reporting bugs.')
95
+ @click.option(
96
+ '--logfile',
97
+ type=str,
98
+ default='',
99
+ help=(
100
+ 'If defined, record information to the specified log file. '
101
+ 'If the file already exists, new logs are appended to the file.'
102
+ ),
103
+ )
104
+ @click.option(
105
+ '--logfile-level',
106
+ type=click.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']),
107
+ default='DEBUG',
108
+ help='The logging level used for the log file.',
109
+ )
110
+ @click.version_option(__version__)
111
+ @click.pass_context
112
+ def subliminal(
113
+ ctx: click.Context,
114
+ /,
115
+ cache_dir: str,
116
+ debug: bool,
117
+ logfile: os.PathLike[str] | None,
118
+ logfile_level: str,
119
+ addic7ed: tuple[str, str],
120
+ opensubtitles: tuple[str, str],
121
+ opensubtitlescom: tuple[str, str],
122
+ **kwargs: Any,
123
+ ) -> None:
124
+ """Subtitles, faster than your thoughts."""
125
+ # make sure to expand user
126
+ cache_dir_path = Path(cache_dir).expanduser()
127
+ # create cache directory
128
+ try:
129
+ cache_dir_path.mkdir(parents=True)
130
+ except OSError: # pragma: no cover
131
+ if not cache_dir_path.is_dir():
132
+ raise
133
+
134
+ # configure cache
135
+ region.configure(
136
+ 'dogpile.cache.dbm',
137
+ expiration_time=timedelta(days=30),
138
+ arguments={'filename': os.fspath(cache_dir_path / cache_file), 'lock_factory': MutexLock},
139
+ )
140
+
141
+ # Set the logger level to DEBUG in case debug or logfile is defined
142
+ subliminal_logger = logging.getLogger('subliminal')
143
+ subliminal_logger.setLevel(logging.DEBUG)
144
+
145
+ # configure logging file
146
+ if logfile:
147
+ # make sure to expand user
148
+ logfile_path = Path(logfile).expanduser()
149
+ try:
150
+ # make sure the parent directories exist
151
+ logfile_path.parent.mkdir(parents=True)
152
+ except OSError: # pragma: no cover
153
+ if not logfile_path.parent.is_dir():
154
+ raise
155
+
156
+ file_handler = logging.FileHandler(logfile_path)
157
+ file_handler.setFormatter(
158
+ logging.Formatter(
159
+ '%(asctime)s | %(levelname)-8s | %(name)s | %(message)s',
160
+ datefmt='%Y-%m-%d %H:%M:%S',
161
+ ),
162
+ )
163
+ file_handler.setLevel(logfile_level)
164
+ subliminal_logger.addHandler(file_handler)
165
+
166
+ # configure logging
167
+ if debug:
168
+ handler = logging.StreamHandler()
169
+ handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
170
+ handler.setLevel(logging.DEBUG)
171
+ subliminal_logger.addHandler(handler)
172
+ # log about the config file
173
+ msg = ctx.obj['debug_message']
174
+ logger.info(msg)
175
+
176
+ ctx.obj['debug'] = debug
177
+
178
+ # create provider and refiner configs
179
+ provider_configs: dict[str, dict[str, Any]] = {}
180
+ refiner_configs: dict[str, dict[str, Any]] = {}
181
+
182
+ for k, v in kwargs.items():
183
+ try_split = k.split('__')
184
+ if len(try_split) != 3: # pragma: no cover
185
+ click.echo(f'Unknown option: {k}={v}')
186
+ continue
187
+ group, plugin, key = try_split
188
+ if group == '_provider':
189
+ provider_configs.setdefault(plugin, {})[key] = v
190
+
191
+ elif group == '_refiner': # pragma: no branch
192
+ refiner_configs.setdefault(plugin, {})[key] = v
193
+
194
+ ctx.obj['provider_configs'] = provider_configs
195
+ ctx.obj['refiner_configs'] = refiner_configs
196
+
197
+ # Deprecated options
198
+ # To be remove in next version
199
+ deprecated_options = {
200
+ 'addic7ed': addic7ed,
201
+ 'opensubtitles': opensubtitles,
202
+ 'opensubtitlescom': opensubtitlescom,
203
+ }
204
+ for provider, option_value in deprecated_options.items():
205
+ if option_value is not None: # pragma: no cover
206
+ msg = (
207
+ f'option --{provider} is deprecated, use --provider.{provider}.username and '
208
+ f'--provider.{provider}.password'
209
+ )
210
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
211
+
212
+ provider_configs[provider]['username'] = option_value[0]
213
+ provider_configs[provider]['password'] = option_value[1]
214
+
215
+
216
+ @subliminal.command()
217
+ @click.option(
218
+ '--clear-subliminal',
219
+ is_flag=True,
220
+ help='Clear subliminal cache. Use this ONLY if your cache is corrupted or if you experience issues.',
221
+ )
222
+ @click.pass_context
223
+ def cache(ctx: click.Context, clear_subliminal: bool) -> None:
224
+ """Cache management."""
225
+ if clear_subliminal and ctx.parent and 'cache_dir' in ctx.parent.params:
226
+ cache_dir_path = Path(ctx.parent.params['cache_dir'])
227
+ for file in (cache_dir_path / cache_file).glob('*'): # pragma: no cover
228
+ file.unlink()
229
+ click.echo("Subliminal's cache cleared.")
230
+ else:
231
+ click.echo('Nothing done.')
232
+
233
+
234
+ # Add commands
235
+ # Download best subtitle
236
+ subliminal.add_command(download)
237
+
238
+
239
+ def cli() -> None: # pragma: no cover
240
+ """CLI that recognizes environment variables."""
241
+ subliminal(auto_envvar_prefix='SUBLIMINAL')
@@ -0,0 +1,5 @@
1
+ """CLI subcommands."""
2
+
3
+ from .download_best import download
4
+
5
+ __all__ = ['download']
@@ -0,0 +1,63 @@
1
+ """Helper functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+ from datetime import timedelta
8
+ from typing import Any
9
+
10
+ import click
11
+ from babelfish import Error as BabelfishError # type: ignore[import-untyped]
12
+ from babelfish import Language
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class LanguageParamType(click.ParamType):
18
+ """:class:`~click.ParamType` for languages that returns a :class:`~babelfish.language.Language`."""
19
+
20
+ name = 'language'
21
+
22
+ def convert(self, value: str, param: click.Parameter | None, ctx: click.Context | None) -> Language:
23
+ """Convert ietf language to :class:`~babelfish.language.Language`."""
24
+ try:
25
+ return Language.fromietf(value)
26
+ except BabelfishError:
27
+ self.fail(f'{value} is not a valid language', param, ctx) # pragma: no cover
28
+
29
+
30
+ class AgeParamType(click.ParamType):
31
+ """:class:`~click.ParamType` for age strings that returns a :class:`~datetime.timedelta`.
32
+
33
+ An age string is in the form `number + identifier` with possible identifiers:
34
+
35
+ * ``w`` for weeks
36
+ * ``d`` for days
37
+ * ``h`` for hours
38
+
39
+ The form can be specified multiple times but only with that identifier ordering. For example:
40
+
41
+ * ``1w2d4h`` for 1 week, 2 days and 4 hours
42
+ * ``2w`` for 2 weeks
43
+ * ``3w6h`` for 3 weeks and 6 hours
44
+ """
45
+
46
+ name = 'age'
47
+
48
+ def convert(self, value: str, param: click.Parameter | None, ctx: click.Context | None) -> timedelta:
49
+ """Convert an age string to :class:`~datetime.timedelta`."""
50
+ match = re.match(r'^(?:(?P<weeks>\d+?)w)?(?:(?P<days>\d+?)d)?(?:(?P<hours>\d+?)h)?$', value)
51
+ if not match:
52
+ self.fail(f'{value} is not a valid age', param, ctx)
53
+
54
+ return timedelta(**{k: int(v) for k, v in match.groupdict(0).items()})
55
+
56
+
57
+ def plural(quantity: int, name: str, *, bold: bool = True, **kwargs: Any) -> str:
58
+ """Format a quantity with plural."""
59
+ return '{} {}{}'.format(
60
+ click.style(str(quantity), bold=bold, **kwargs),
61
+ name,
62
+ 's' if quantity > 1 else '',
63
+ )