subliminal 2.5.0__tar.gz → 2.6.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.
- {subliminal-2.5.0 → subliminal-2.6.0}/HISTORY.rst +23 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/PKG-INFO +6 -2
- {subliminal-2.5.0 → subliminal-2.6.0}/README.rst +5 -1
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/providers.rst +5 -0
- subliminal-2.6.0/docs/assets/demo.gif +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/user/configuration_file.rst +1 -1
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/user/how_it_works.rst +1 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/pyproject.toml +1 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/extensions.py +1 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/__init__.py +1 -2
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/bsplayer.py +1 -1
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/napiprojekt.py +1 -2
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/opensubtitles.py +0 -1
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/opensubtitlescom.py +20 -25
- subliminal-2.6.0/src/subliminal/providers/subtis.py +336 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/subtitulamos.py +7 -3
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/refiners/hash.py +12 -8
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/refiners/tvdb.py +1 -23
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/utils.py +4 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_get_id.yaml +15 -17
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_get_title.yaml +15 -17
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_get_wrong_id.yaml +11 -13
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_get_wrong_title.yaml +15 -17
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_refine_episode.yaml +11 -13
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_refine_episode_original_series.yaml +68 -80
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_refine_episode_with_country.yaml +17 -19
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_refine_episode_with_country_hoc_us.yaml +15 -18
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_refine_episode_year.yaml +11 -13
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_refine_movie.yaml +49 -54
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_refine_movie_guess_alternative_title.yaml +15 -16
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_search.yaml +11 -13
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_search_page.yaml +11 -13
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_search_type.yaml +11 -13
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_search_wrong_title.yaml +15 -17
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/omdb/test_search_year.yaml +11 -13
- subliminal-2.6.0/tests/cassettes/subtis/test_download_subtitle.yaml +7982 -0
- subliminal-2.6.0/tests/cassettes/subtis/test_list_subtitles_hash.yaml +258 -0
- subliminal-2.6.0/tests/cassettes/subtis/test_list_subtitles_movie.yaml +997 -0
- subliminal-2.6.0/tests/cassettes/subtis/test_list_subtitles_movie_alternative.yaml +682 -0
- subliminal-2.6.0/tests/cassettes/subtis/test_list_subtitles_movie_with_size.yaml +367 -0
- subliminal-2.6.0/tests/cassettes/subtis/test_list_subtitles_no_match.yaml +632 -0
- subliminal-2.6.0/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_series.yaml +110 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/conftest.py +2 -2
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_providers.py +3 -3
- subliminal-2.6.0/tests/providers/test_subtis.py +390 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/refiners/test_hash.py +1 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/refiners/test_omdb.py +13 -10
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/refiners/test_tvdb.py +0 -22
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_utils.py +1 -0
- subliminal-2.5.0/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_series.yaml +0 -114
- subliminal-2.5.0/tests/cassettes/tvdb/test_refresh_token.yaml +0 -100
- subliminal-2.5.0/tests/cassettes/tvdb/test_token_needs_refresh.yaml +0 -50
- {subliminal-2.5.0 → subliminal-2.6.0}/.dockerignore +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/actions/prepare-tests/action.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/CI.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/coverage.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/docker-build.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/prepare-release-pr.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/publish-release.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/publish.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/scheduled-tests.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/workflows/tag-release.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.github/zizmor.yml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.gitignore +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.pre-commit-config.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.python-version-default +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/.readthedocs.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/CONTRIBUTING.md +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/Dockerfile +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/LICENSE +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/MANIFEST.in +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/RELEASING.md +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/changelog.d/.gitignore +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/Makefile +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/_intersphinx/README.md +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/_intersphinx/importlib_metadata_objects.inv +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/_intersphinx/importlib_metadata_objects.txt +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/_static/.gitignore +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/cache.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/cli.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/core.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/exceptions.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/extensions.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/refiners.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/score.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/subtitle.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/utils.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/api/video.rst +0 -0
- {subliminal-2.5.0/docs → subliminal-2.6.0/docs/assets}/config.toml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/cassettes/test_usage.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/conf.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/conftest.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/index.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/make.bat +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/user/changelog.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/user/cli.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/user/installation.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/user/provider_guide.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/docs/user/usage.rst +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/hatch.toml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/scripts/.gitignore +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/scripts/prepare-release-pr.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/scripts/prepare_tests.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/scripts/release.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/__init__.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/__main__.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/archives.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cache.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cli/__init__.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cli/cli.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cli/commands/__init__.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cli/commands/_format.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cli/commands/download_best.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cli/generate_config.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/cli/helpers.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/converters/__init__.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/converters/addic7ed.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/converters/opensubtitles.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/converters/opensubtitlescom.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/converters/subtitulamos.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/converters/tvsubtitles.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/core.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/exceptions.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/matches.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/addic7ed.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/gestdown.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/mock.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/podnapisi.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/providers/tvsubtitles.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/py.typed +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/refiners/__init__.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/refiners/metadata.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/refiners/omdb.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/refiners/tmdb.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/score.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/subtitle.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/src/subliminal/video.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/__init__.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_get_show_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_get_show_id_country.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_get_show_id_quote_dots_mixed_case.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_get_show_id_with_comma.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_get_show_id_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_get_show_ids.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_list_subtitles.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_list_subtitles_episode_alternative_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_list_subtitles_show_with_asterisk.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query_no_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query_parsing.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query_parsing_colon.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query_parsing_dash.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query_parsing_quote_dots_mixed_case.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query_wrong_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/addic7ed/test_query_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/bsplayer/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/bsplayer/test_list_subtitles_hash.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/bsplayer/test_login.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/bsplayer/test_logout.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/bsplayer/test_query_hash_size.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/cli/test_cli_download.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_download_with_bom.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_get_title_and_show_id_alternative_name.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_get_title_and_show_id_no_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_get_title_and_show_id_only_title.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_get_title_and_show_id_with_tvdb_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_list_subtitles.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_list_subtitles_episode_alternative_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query_all_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query_no_language.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query_parsing.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query_parsing_colon.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query_parsing_dash.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query_parsing_quote_dots_mixed_case.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_query_wrong_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_search_show_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_search_show_id_error.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_search_show_id_incomplete.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_search_show_id_no_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_search_show_id_quote.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_search_show_series_tvdb_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/gestdown/test_show_with_asterisk.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/napiprojekt/test_download_subtitles.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/napiprojekt/test_list_subtitles.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/napiprojekt/test_query_microdvd.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/napiprojekt/test_query_srt.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/napiprojekt/test_query_srt_reencode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/napiprojekt/test_query_wrong_hash.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_list_subtitles_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_list_subtitles_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_list_subtitles_movie_no_hash.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_login_bad_password.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_login_vip_bad_password.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_login_vip_login.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_no_operation.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_query_hash_size.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_query_imdb_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_query_not_enough_information.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_query_query_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_query_query_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_query_query_season_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitles/test_query_wrong_hash_wrong_size.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_list_subtitles_movie_no_hash.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_login.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_login_bad_password.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_logout.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_hash_size.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_imdb_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_max_result_pages.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_query_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_query_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_query_season_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_tag_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_query_wrong_hash_wrong_size.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_tag_match.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/opensubtitlescom/test_user_infos.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/podnapisi/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/podnapisi/test_download_subtitles_with_title_unicode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/podnapisi/test_list_subtitles_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/podnapisi/test_list_subtitles_episode_alternative_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/podnapisi/test_list_subtitles_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/podnapisi/test_query_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/podnapisi/test_query_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/providers/test_download_best_subtitles.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/providers/test_download_best_subtitles_min_score.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/providers/test_download_best_subtitles_only_one.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/providers/test_list_subtitles_providers_download.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_download_subtitle_first_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_download_subtitle_foo.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_download_subtitle_last_season.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_download_subtitle_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_language.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_list_subtitles_not_exist_season.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/subtitulamos/test_list_subtitles_with_spanish_non_mx_search.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_get_movie_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_get_movie_id_failed.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_get_series_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_get_series_wrong_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_refine_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_refine_episode_original_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_refine_episode_with_country.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_refine_episode_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_refine_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_refine_movie_guess_alternative_title.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_search_movie.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_search_movie_page.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_search_movie_wrong_title.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_search_movie_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_search_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tmdb/test_search_series_wrong_name.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_get_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_get_series_actors.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_get_series_actors_wrong_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_get_series_wrong_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_login.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_login_error.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_query_series_episodes.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_query_series_episodes_wrong_season.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_ambiguous.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_ambiguous_2.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_episode_alternative_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_episode_no_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_episode_partial.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_episode_with_comma.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_episode_with_country.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_episode_with_country_hoc_us.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_refine_episode_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_search_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvdb/test_search_series_wrong_name.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_download_subtitle.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_download_subtitle_with_quote.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_get_episode_ids.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_get_episode_ids_wrong_season.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_list_subtitles.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_list_subtitles_episode_alternative_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_query.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_query_no_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_query_with_quote.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_query_wrong_episode.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_query_wrong_series.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id_ambiguous.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id_error.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id_incomplete.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id_no_year.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id_uk.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id_us.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cassettes/tvsubtitles/test_search_show_id_year_in_title.yaml +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cli/test_cli.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cli/test_download.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cli/test_generate_config.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/cli/test_helpers.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_addic7ed.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_bsplayer.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_gestdown.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_napiprojekt.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_opensubtitles.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_opensubtitlescom.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_podnapisi.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_subtitulamos.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/providers/test_tvsubtitles.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/refiners/test_metadata.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/refiners/test_tmdb.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_archives.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_cache.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_core.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_extensions.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_matches.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_provider.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_provider_pool.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_score.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_subtitle.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tests/test_video.py +0 -0
- {subliminal-2.5.0 → subliminal-2.6.0}/tox.ini +0 -0
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
2.6.0 (2026-02-20)
|
|
6
|
+
^^^^^^^^^^^^^^^^^^
|
|
7
|
+
|
|
8
|
+
Provider Changes
|
|
9
|
+
----------------
|
|
10
|
+
|
|
11
|
+
- OpenSubtitlesCom: replace recursive ``_search()`` implementation to prevent a stack overflow in some edge case (`#1336 <https://github.com/Diaoul/subliminal/issues/1336>`__)
|
|
12
|
+
- Add Subtis provider for Spanish subtitles (`#1337 <https://github.com/Diaoul/subliminal/issues/1337>`__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Refiner Changes
|
|
16
|
+
---------------
|
|
17
|
+
|
|
18
|
+
- hash: cache the result of hash_opensubtitles so it can be reused costlessly (`#1340 <https://github.com/Diaoul/subliminal/issues/1340>`__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Misc
|
|
22
|
+
----
|
|
23
|
+
|
|
24
|
+
- Fixes for some scheduled tests failures (`#1339 <https://github.com/Diaoul/subliminal/issues/1339>`__)
|
|
25
|
+
- add a gif to show CLI usage (`#1341 <https://github.com/Diaoul/subliminal/issues/1341>`__)
|
|
26
|
+
|
|
27
|
+
|
|
5
28
|
2.5.0 (2025-12-14)
|
|
6
29
|
^^^^^^^^^^^^^^^^^^
|
|
7
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: subliminal
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.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
|
|
@@ -103,6 +103,10 @@ Subtitles, faster than your thoughts.
|
|
|
103
103
|
|
|
104
104
|
Usage
|
|
105
105
|
-----
|
|
106
|
+
|
|
107
|
+
.. image:: https://github.com/Diaoul/subliminal/blob/main/docs/assets/demo.gif
|
|
108
|
+
:alt: Demo of the app CLI usage
|
|
109
|
+
|
|
106
110
|
CLI
|
|
107
111
|
^^^
|
|
108
112
|
Download English subtitles::
|
|
@@ -120,7 +124,7 @@ By default it looks for a `subliminal.toml` file in the default configuration fo
|
|
|
120
124
|
(see the CLI help for the exact platform-specific default path).
|
|
121
125
|
Or use the `-c` option to specify the path to the configuration file.
|
|
122
126
|
|
|
123
|
-
`Look for this example configuration file <https://github.com/Diaoul/subliminal/blob/main/docs/config.toml>`__
|
|
127
|
+
`Look for this example configuration file <https://github.com/Diaoul/subliminal/blob/main/docs/assets/config.toml>`__
|
|
124
128
|
or use the `generate_default_config` function from the `subliminal.cli` module to generate a
|
|
125
129
|
configuration file with all the options and their default values::
|
|
126
130
|
|
|
@@ -30,6 +30,10 @@ Subtitles, faster than your thoughts.
|
|
|
30
30
|
|
|
31
31
|
Usage
|
|
32
32
|
-----
|
|
33
|
+
|
|
34
|
+
.. image:: https://github.com/Diaoul/subliminal/blob/main/docs/assets/demo.gif
|
|
35
|
+
:alt: Demo of the app CLI usage
|
|
36
|
+
|
|
33
37
|
CLI
|
|
34
38
|
^^^
|
|
35
39
|
Download English subtitles::
|
|
@@ -47,7 +51,7 @@ By default it looks for a `subliminal.toml` file in the default configuration fo
|
|
|
47
51
|
(see the CLI help for the exact platform-specific default path).
|
|
48
52
|
Or use the `-c` option to specify the path to the configuration file.
|
|
49
53
|
|
|
50
|
-
`Look for this example configuration file <https://github.com/Diaoul/subliminal/blob/main/docs/config.toml>`__
|
|
54
|
+
`Look for this example configuration file <https://github.com/Diaoul/subliminal/blob/main/docs/assets/config.toml>`__
|
|
51
55
|
or use the `generate_default_config` function from the `subliminal.cli` module to generate a
|
|
52
56
|
configuration file with all the options and their default values::
|
|
53
57
|
|
|
Binary file
|
|
@@ -106,6 +106,7 @@ opensubtitlesvip = "subliminal.providers.opensubtitles:OpenSubtitlesVipProvider"
|
|
|
106
106
|
opensubtitlescom = "subliminal.providers.opensubtitlescom:OpenSubtitlesComProvider"
|
|
107
107
|
opensubtitlescomvip = "subliminal.providers.opensubtitlescom:OpenSubtitlesComVipProvider"
|
|
108
108
|
podnapisi = "subliminal.providers.podnapisi:PodnapisiProvider"
|
|
109
|
+
subtis = "subliminal.providers.subtis:SubtisProvider"
|
|
109
110
|
subtitulamos = "subliminal.providers.subtitulamos:SubtitulamosProvider"
|
|
110
111
|
tvsubtitles = "subliminal.providers.tvsubtitles:TVsubtitlesProvider"
|
|
111
112
|
|
|
@@ -157,6 +157,7 @@ provider_manager = RegistrableExtensionManager(
|
|
|
157
157
|
'opensubtitlescomvip = subliminal.providers.opensubtitlescom:OpenSubtitlesComVipProvider',
|
|
158
158
|
'opensubtitlesvip = subliminal.providers.opensubtitles:OpenSubtitlesVipProvider',
|
|
159
159
|
'podnapisi = subliminal.providers.podnapisi:PodnapisiProvider',
|
|
160
|
+
'subtis = subliminal.providers.subtis:SubtisProvider',
|
|
160
161
|
'subtitulamos = subliminal.providers.subtitulamos:SubtitulamosProvider',
|
|
161
162
|
'tvsubtitles = subliminal.providers.tvsubtitles:TVsubtitlesProvider',
|
|
162
163
|
],
|
|
@@ -24,7 +24,6 @@ from subliminal.subtitle import Subtitle
|
|
|
24
24
|
from subliminal.video import Episode, Movie, Video
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
|
-
import os
|
|
28
27
|
from collections.abc import Sequence, Set
|
|
29
28
|
from http.client import HTTPSConnection
|
|
30
29
|
from types import TracebackType
|
|
@@ -146,7 +145,7 @@ class Provider(Generic[S]):
|
|
|
146
145
|
user_agent: str = f'Subliminal/{__short_version__}'
|
|
147
146
|
|
|
148
147
|
@staticmethod
|
|
149
|
-
def hash_video(video_path: str
|
|
148
|
+
def hash_video(video_path: str) -> str | None:
|
|
150
149
|
"""Hash the video to be used by the provider."""
|
|
151
150
|
return None
|
|
152
151
|
|
|
@@ -200,7 +200,7 @@ class BSPlayerProvider(Provider):
|
|
|
200
200
|
self.search_url = search_url or get_sub_domain()
|
|
201
201
|
|
|
202
202
|
@staticmethod
|
|
203
|
-
def hash_video(video_path: str
|
|
203
|
+
def hash_video(video_path: str) -> str | None:
|
|
204
204
|
"""Compute a hash using BSPlayer algorithm.
|
|
205
205
|
|
|
206
206
|
:param str video_path: path of the video.
|
|
@@ -17,7 +17,6 @@ from subliminal.subtitle import Subtitle, fix_line_ending
|
|
|
17
17
|
from . import Provider
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
|
-
import os
|
|
21
20
|
from collections.abc import Set
|
|
22
21
|
|
|
23
22
|
from subliminal.video import Video
|
|
@@ -98,7 +97,7 @@ class NapiProjektProvider(Provider):
|
|
|
98
97
|
self.session = None
|
|
99
98
|
|
|
100
99
|
@staticmethod
|
|
101
|
-
def hash_video(video_path: str
|
|
100
|
+
def hash_video(video_path: str) -> str | None:
|
|
102
101
|
"""Compute a hash using NapiProjekt's algorithm.
|
|
103
102
|
|
|
104
103
|
:param str video_path: path of the video.
|
|
@@ -196,7 +196,6 @@ class OpenSubtitlesProvider(Provider):
|
|
|
196
196
|
username: str | None = None,
|
|
197
197
|
password: str | None = None,
|
|
198
198
|
*,
|
|
199
|
-
max_result_pages: int = 0,
|
|
200
199
|
timeout: int = 10,
|
|
201
200
|
) -> None:
|
|
202
201
|
transport = TimeoutSafeTransport(timeout=timeout, user_agent='VLSub')
|
|
@@ -31,7 +31,7 @@ from subliminal.video import Episode, Movie, Video
|
|
|
31
31
|
from . import Provider
|
|
32
32
|
|
|
33
33
|
if TYPE_CHECKING:
|
|
34
|
-
from collections.abc import Mapping, Set
|
|
34
|
+
from collections.abc import Generator, Mapping, Set
|
|
35
35
|
|
|
36
36
|
C = TypeVar('C', bound=Callable)
|
|
37
37
|
|
|
@@ -595,30 +595,28 @@ class OpenSubtitlesComProvider(Provider):
|
|
|
595
595
|
|
|
596
596
|
return r.json() # type: ignore[no-any-return]
|
|
597
597
|
|
|
598
|
-
def _search(self,
|
|
599
|
-
|
|
600
|
-
ext_params = {'page': page, **params}
|
|
598
|
+
def _search(self, **params: Any) -> Generator[dict[str, Any], None, None]:
|
|
599
|
+
page = 1
|
|
601
600
|
|
|
602
|
-
|
|
603
|
-
|
|
601
|
+
while True:
|
|
602
|
+
# Extended params with page
|
|
603
|
+
ext_params = {'page': page, **params}
|
|
604
|
+
logger.info('Searching subtitles %r', ext_params)
|
|
605
|
+
# GET request and add page information
|
|
606
|
+
response = self.api_get('subtitles', ext_params)
|
|
607
|
+
if not response or not response['data']:
|
|
608
|
+
break
|
|
609
|
+
yield from response['data']
|
|
604
610
|
|
|
605
|
-
|
|
606
|
-
response = self.api_get('subtitles', ext_params)
|
|
611
|
+
page += 1
|
|
607
612
|
|
|
608
|
-
|
|
609
|
-
|
|
613
|
+
# check that the maximum number of pages has not been exceeded
|
|
614
|
+
if self.max_result_pages > 0 and page > self.max_result_pages:
|
|
615
|
+
break
|
|
610
616
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
allow_more_pages = self.max_result_pages <= 0 or page < self.max_result_pages
|
|
615
|
-
|
|
616
|
-
# retrieve other pages maybe
|
|
617
|
-
if allow_more_pages and 'total_pages' in response and page < response['total_pages']:
|
|
618
|
-
# missing pages
|
|
619
|
-
ret.extend(self._search(page=page + 1, **params))
|
|
620
|
-
|
|
621
|
-
return ret # type: ignore[no-any-return]
|
|
617
|
+
# check if we fetched all pages already
|
|
618
|
+
if 'total_pages' not in response or page > response['total_pages']:
|
|
619
|
+
break
|
|
622
620
|
|
|
623
621
|
def _make_query(
|
|
624
622
|
self,
|
|
@@ -711,14 +709,11 @@ class OpenSubtitlesComProvider(Provider):
|
|
|
711
709
|
# add the language and query the server
|
|
712
710
|
criterion.update({'languages': ','.join(sorted(lang.opensubtitlescom for lang in languages))})
|
|
713
711
|
|
|
714
|
-
# query the server
|
|
715
|
-
responses = self._search(**criterion)
|
|
716
|
-
|
|
717
712
|
imdb_match = 'imdb_id' in criterion or 'show_imdb_id' in criterion
|
|
718
713
|
tmdb_match = 'tmdb_id' in criterion or 'show_tmdb_id' in criterion
|
|
719
714
|
|
|
720
715
|
# loop over subtitle items
|
|
721
|
-
for response in
|
|
716
|
+
for response in self._search(**criterion):
|
|
722
717
|
# read single response
|
|
723
718
|
subtitle = self.subtitle_class.from_response(
|
|
724
719
|
response,
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Provider for Subtis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar, cast
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
from babelfish import Language
|
|
11
|
+
from guessit import guessit
|
|
12
|
+
from requests import Session
|
|
13
|
+
from requests.exceptions import HTTPError, JSONDecodeError, RequestException
|
|
14
|
+
|
|
15
|
+
from subliminal.exceptions import NotInitializedProviderError, ProviderError
|
|
16
|
+
from subliminal.matches import guess_matches
|
|
17
|
+
from subliminal.subtitle import Subtitle
|
|
18
|
+
from subliminal.video import Movie
|
|
19
|
+
|
|
20
|
+
from . import Provider
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Set
|
|
24
|
+
|
|
25
|
+
from subliminal.video import Video
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
subtis_languages: set[Language] = {
|
|
30
|
+
Language('spa', country)
|
|
31
|
+
for country in [
|
|
32
|
+
'AR',
|
|
33
|
+
'BO',
|
|
34
|
+
'CL',
|
|
35
|
+
'CO',
|
|
36
|
+
'CR',
|
|
37
|
+
'DO',
|
|
38
|
+
'EC',
|
|
39
|
+
'GT',
|
|
40
|
+
'HN',
|
|
41
|
+
'MX',
|
|
42
|
+
'NI',
|
|
43
|
+
'PA',
|
|
44
|
+
'PE',
|
|
45
|
+
'PR',
|
|
46
|
+
'PY',
|
|
47
|
+
'SV',
|
|
48
|
+
'US',
|
|
49
|
+
'UY',
|
|
50
|
+
'VE',
|
|
51
|
+
]
|
|
52
|
+
} | {Language('spa')}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SubtisSubtitle(Subtitle):
|
|
56
|
+
"""Subtis Subtitle."""
|
|
57
|
+
|
|
58
|
+
provider_name: ClassVar[str] = 'subtis'
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
language: Language,
|
|
63
|
+
subtitle_id: str = '',
|
|
64
|
+
*,
|
|
65
|
+
page_link: str | None = None,
|
|
66
|
+
title: str | None = None,
|
|
67
|
+
download_link: str | None = None,
|
|
68
|
+
is_synced: bool = True,
|
|
69
|
+
video_hash: str | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
super().__init__(
|
|
72
|
+
language=language,
|
|
73
|
+
subtitle_id=subtitle_id,
|
|
74
|
+
hearing_impaired=False,
|
|
75
|
+
page_link=page_link,
|
|
76
|
+
)
|
|
77
|
+
self.title = title
|
|
78
|
+
self.download_link = download_link
|
|
79
|
+
self.is_synced = is_synced
|
|
80
|
+
self.video_hash = video_hash
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def info(self) -> str:
|
|
84
|
+
"""Information about the subtitle."""
|
|
85
|
+
if not self.title:
|
|
86
|
+
return self.id
|
|
87
|
+
sync_indicator = '' if self.is_synced else ' [fuzzy match]'
|
|
88
|
+
return f'{self.title}{sync_indicator}'
|
|
89
|
+
|
|
90
|
+
def get_matches(self, video: Video) -> set[str]:
|
|
91
|
+
"""Get the matches against the `video`."""
|
|
92
|
+
matches: set[str] = set()
|
|
93
|
+
|
|
94
|
+
if not isinstance(video, Movie):
|
|
95
|
+
return matches
|
|
96
|
+
|
|
97
|
+
if self.video_hash and 'subtis' in video.hashes and self.video_hash == video.hashes['subtis']:
|
|
98
|
+
matches.add('hash')
|
|
99
|
+
|
|
100
|
+
if self.is_synced and video.name:
|
|
101
|
+
matches |= guess_matches(video, guessit(os.path.basename(video.name), {'type': 'movie'}))
|
|
102
|
+
elif self.title:
|
|
103
|
+
matches |= guess_matches(video, guessit(self.title, {'type': 'movie'}))
|
|
104
|
+
|
|
105
|
+
return matches
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class SubtisProvider(Provider[SubtisSubtitle]):
|
|
109
|
+
"""Subtis Provider.
|
|
110
|
+
|
|
111
|
+
Provides Spanish subtitles for movies from the subt.is API.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
languages: ClassVar[Set[Language]] = subtis_languages
|
|
115
|
+
video_types: ClassVar = (Movie,)
|
|
116
|
+
subtitle_class: ClassVar = SubtisSubtitle
|
|
117
|
+
|
|
118
|
+
server_url: ClassVar[str] = 'https://api.subt.is/v1'
|
|
119
|
+
|
|
120
|
+
timeout: int
|
|
121
|
+
session: Session | None
|
|
122
|
+
|
|
123
|
+
def __init__(self, *, timeout: int = 10) -> None:
|
|
124
|
+
self.timeout = timeout
|
|
125
|
+
self.session = None
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def hash_video(video_path: str) -> str | None:
|
|
129
|
+
"""Compute a hash using OpenSubtitles' algorithm.
|
|
130
|
+
|
|
131
|
+
:param video_path: path of the video file.
|
|
132
|
+
:return: the hash string, or None if file is too small or error occurs.
|
|
133
|
+
"""
|
|
134
|
+
from subliminal.refiners.hash import hash_opensubtitles
|
|
135
|
+
|
|
136
|
+
video_path = os.fspath(video_path)
|
|
137
|
+
try:
|
|
138
|
+
return hash_opensubtitles(video_path)
|
|
139
|
+
except OSError:
|
|
140
|
+
logger.debug('Could not compute hash for %s', video_path)
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def initialize(self) -> None:
|
|
144
|
+
"""Initialize the provider."""
|
|
145
|
+
self.session = Session()
|
|
146
|
+
self.session.headers['User-Agent'] = self.user_agent
|
|
147
|
+
self.session.headers['Accept'] = 'application/json'
|
|
148
|
+
|
|
149
|
+
def terminate(self) -> None:
|
|
150
|
+
"""Terminate the provider."""
|
|
151
|
+
if self.session is None:
|
|
152
|
+
raise NotInitializedProviderError
|
|
153
|
+
self.session.close()
|
|
154
|
+
self.session = None
|
|
155
|
+
|
|
156
|
+
def _session_request(self, url: str) -> dict[str, Any] | None:
|
|
157
|
+
"""Perform a GET request to the provider."""
|
|
158
|
+
if self.session is None:
|
|
159
|
+
raise NotInitializedProviderError
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
r = self.session.get(url, timeout=self.timeout)
|
|
163
|
+
except RequestException:
|
|
164
|
+
logger.exception('Request error for %s', url)
|
|
165
|
+
raise SubtisError from None
|
|
166
|
+
|
|
167
|
+
if r.status_code in (400, 404):
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
r.raise_for_status()
|
|
172
|
+
except HTTPError:
|
|
173
|
+
msg = f'Unexpected status {r.status_code}'
|
|
174
|
+
logger.warning('%s for %s', msg, url)
|
|
175
|
+
raise SubtisError(msg) from None
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
return cast('dict[str, Any]', r.json())
|
|
179
|
+
except JSONDecodeError:
|
|
180
|
+
msg = 'Invalid JSON response'
|
|
181
|
+
logger.exception('%s for %s', msg, url)
|
|
182
|
+
raise SubtisError(msg) from None
|
|
183
|
+
|
|
184
|
+
def _parse_response(self, data: dict[str, Any]) -> tuple[str, str] | None:
|
|
185
|
+
"""Parse API response and extract subtitle link and title.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Tuple of (subtitle_link, title_name), or None if required fields are missing.
|
|
189
|
+
"""
|
|
190
|
+
subtitle_data = data.get('subtitle')
|
|
191
|
+
if not isinstance(subtitle_data, dict):
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
subtitle_link = subtitle_data.get('subtitle_link')
|
|
195
|
+
if not isinstance(subtitle_link, str) or not subtitle_link:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
title_data = data.get('title', {})
|
|
199
|
+
title_name = title_data.get('title_name', 'Unknown') if isinstance(title_data, dict) else 'Unknown'
|
|
200
|
+
|
|
201
|
+
return subtitle_link, str(title_name)
|
|
202
|
+
|
|
203
|
+
def query(self, video: Movie, languages: Set[Language]) -> list[SubtisSubtitle]:
|
|
204
|
+
"""Query the provider for subtitles using cascade search.
|
|
205
|
+
|
|
206
|
+
Cascade order (from most to least specific):
|
|
207
|
+
1. Hash - OpenSubtitles video hash (most precise)
|
|
208
|
+
2. Bytes - File size match
|
|
209
|
+
3. Filename - Exact filename match
|
|
210
|
+
4. Alternative - Fuzzy match (fallback)
|
|
211
|
+
"""
|
|
212
|
+
if not video.name:
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
language = next((lang for lang in languages if lang.alpha3 == 'spa'), Language('spa'))
|
|
216
|
+
|
|
217
|
+
filename = os.path.basename(video.name)
|
|
218
|
+
encoded_filename = quote(filename, safe='')
|
|
219
|
+
|
|
220
|
+
if video.name and os.path.isfile(video.name):
|
|
221
|
+
video_hash = video.hashes.get('subtis')
|
|
222
|
+
if video_hash:
|
|
223
|
+
hash_url = f'{self.server_url}/subtitle/find/file/hash/{video_hash}'
|
|
224
|
+
logger.info('Searching subtitles by hash for %s', filename)
|
|
225
|
+
data = self._session_request(hash_url)
|
|
226
|
+
if data:
|
|
227
|
+
parsed = self._parse_response(data)
|
|
228
|
+
if parsed:
|
|
229
|
+
subtitle_link, title_name = parsed
|
|
230
|
+
logger.debug('Found subtitle via hash search')
|
|
231
|
+
return [
|
|
232
|
+
SubtisSubtitle(
|
|
233
|
+
language=language,
|
|
234
|
+
subtitle_id=subtitle_link,
|
|
235
|
+
page_link=hash_url,
|
|
236
|
+
title=title_name,
|
|
237
|
+
download_link=subtitle_link,
|
|
238
|
+
is_synced=True,
|
|
239
|
+
video_hash=video_hash,
|
|
240
|
+
)
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
if video.size:
|
|
244
|
+
bytes_url = f'{self.server_url}/subtitle/find/file/bytes/{video.size}'
|
|
245
|
+
logger.info('Searching subtitles by bytes for %s', filename)
|
|
246
|
+
data = self._session_request(bytes_url)
|
|
247
|
+
if data:
|
|
248
|
+
parsed = self._parse_response(data)
|
|
249
|
+
if parsed:
|
|
250
|
+
subtitle_link, title_name = parsed
|
|
251
|
+
logger.debug('Found subtitle via bytes search')
|
|
252
|
+
return [
|
|
253
|
+
SubtisSubtitle(
|
|
254
|
+
language=language,
|
|
255
|
+
subtitle_id=subtitle_link,
|
|
256
|
+
page_link=bytes_url,
|
|
257
|
+
title=title_name,
|
|
258
|
+
download_link=subtitle_link,
|
|
259
|
+
is_synced=True,
|
|
260
|
+
)
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
filename_url = f'{self.server_url}/subtitle/find/file/name/{encoded_filename}'
|
|
264
|
+
logger.info('Searching subtitles by filename for %s', filename)
|
|
265
|
+
data = self._session_request(filename_url)
|
|
266
|
+
if data:
|
|
267
|
+
parsed = self._parse_response(data)
|
|
268
|
+
if parsed:
|
|
269
|
+
subtitle_link, title_name = parsed
|
|
270
|
+
logger.debug('Found subtitle via filename search')
|
|
271
|
+
return [
|
|
272
|
+
SubtisSubtitle(
|
|
273
|
+
language=language,
|
|
274
|
+
subtitle_id=subtitle_link,
|
|
275
|
+
page_link=filename_url,
|
|
276
|
+
title=title_name,
|
|
277
|
+
download_link=subtitle_link,
|
|
278
|
+
is_synced=True,
|
|
279
|
+
)
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
alternative_url = f'{self.server_url}/subtitle/file/alternative/{encoded_filename}'
|
|
283
|
+
logger.info('Searching subtitles (alternative) for %s', filename)
|
|
284
|
+
data = self._session_request(alternative_url)
|
|
285
|
+
if data:
|
|
286
|
+
parsed = self._parse_response(data)
|
|
287
|
+
if parsed:
|
|
288
|
+
subtitle_link, title_name = parsed
|
|
289
|
+
logger.debug('Found subtitle via alternative search (fuzzy)')
|
|
290
|
+
return [
|
|
291
|
+
SubtisSubtitle(
|
|
292
|
+
language=language,
|
|
293
|
+
subtitle_id=subtitle_link,
|
|
294
|
+
page_link=alternative_url,
|
|
295
|
+
title=title_name,
|
|
296
|
+
download_link=subtitle_link,
|
|
297
|
+
is_synced=False,
|
|
298
|
+
)
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
logger.info('No subtitle found for %s', filename)
|
|
302
|
+
return []
|
|
303
|
+
|
|
304
|
+
def list_subtitles(self, video: Video, languages: Set[Language]) -> list[SubtisSubtitle]:
|
|
305
|
+
"""List all the subtitles for the video."""
|
|
306
|
+
if not isinstance(video, Movie):
|
|
307
|
+
return []
|
|
308
|
+
|
|
309
|
+
return self.query(video, languages)
|
|
310
|
+
|
|
311
|
+
def download_subtitle(self, subtitle: SubtisSubtitle) -> None:
|
|
312
|
+
"""Download the content of the subtitle."""
|
|
313
|
+
if self.session is None:
|
|
314
|
+
raise NotInitializedProviderError
|
|
315
|
+
|
|
316
|
+
if not subtitle.download_link:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
logger.info('Downloading subtitle %s', subtitle.download_link)
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
r = self.session.get(subtitle.download_link, timeout=self.timeout)
|
|
323
|
+
r.raise_for_status()
|
|
324
|
+
except RequestException:
|
|
325
|
+
logger.exception('Download error for %s', subtitle.download_link)
|
|
326
|
+
raise SubtisError from None
|
|
327
|
+
|
|
328
|
+
if not r.content:
|
|
329
|
+
logger.warning('Empty subtitle content from %s', subtitle.download_link)
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
subtitle.set_content(r.content)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class SubtisError(ProviderError):
|
|
336
|
+
"""Base class for non-generic :class:`SubtisProvider` exceptions."""
|
|
@@ -182,21 +182,25 @@ class SubtitulamosProvider(Provider):
|
|
|
182
182
|
|
|
183
183
|
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)
|
|
184
184
|
def _read_episode_page(
|
|
185
|
-
self,
|
|
185
|
+
self,
|
|
186
|
+
series: str,
|
|
187
|
+
season: int,
|
|
188
|
+
episode: int,
|
|
189
|
+
year: int | None = None,
|
|
186
190
|
) -> tuple[ParserBeautifulSoup, str]:
|
|
187
191
|
"""Search the URL titles by kind for the given `title`, `season` and `episode`.
|
|
188
192
|
|
|
189
193
|
:param str series: Series to search for.
|
|
190
194
|
:param int season: Season to search for.
|
|
191
195
|
:param int episode: Episode to search for.
|
|
192
|
-
:param int year: Year to search for.
|
|
196
|
+
:param (int | None) year: Year to search for.
|
|
193
197
|
:return: The episode URL.
|
|
194
198
|
|
|
195
199
|
"""
|
|
196
200
|
logger.info('Searching episode url for %s, season %d, episode %d', series, season, episode)
|
|
197
201
|
|
|
198
202
|
# attempt first with year
|
|
199
|
-
series_response = self._query_search(f'{series} ({year})')
|
|
203
|
+
series_response = self._query_search(f'{series} ({year})') if year else []
|
|
200
204
|
if len(series_response) == 0:
|
|
201
205
|
series_response = self._query_search(series)
|
|
202
206
|
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
import struct
|
|
8
|
+
from functools import cache
|
|
8
9
|
from typing import TYPE_CHECKING, Any, cast
|
|
9
10
|
|
|
10
11
|
from subliminal.extensions import get_default_providers, provider_manager
|
|
@@ -18,23 +19,26 @@ if TYPE_CHECKING:
|
|
|
18
19
|
from subliminal.providers import Provider
|
|
19
20
|
from subliminal.video import Video
|
|
20
21
|
|
|
21
|
-
HashFunc: TypeAlias = Callable[[str
|
|
22
|
+
HashFunc: TypeAlias = Callable[[str], str | None]
|
|
22
23
|
|
|
23
24
|
logger = logging.getLogger(__name__)
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
@cache
|
|
28
|
+
def hash_opensubtitles(video_name: str) -> str | None:
|
|
27
29
|
"""Compute a hash using OpenSubtitles' algorithm.
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
:
|
|
31
|
+
The result is cached so it can be reused by several providers without cost.
|
|
32
|
+
|
|
33
|
+
:param str video_name: filename of the video.
|
|
34
|
+
:return: the hash or None if it failed.
|
|
35
|
+
:rtype: str | None
|
|
32
36
|
|
|
33
37
|
"""
|
|
34
|
-
|
|
38
|
+
video_name = os.fspath(video_name)
|
|
35
39
|
bytesize = struct.calcsize(b'<q')
|
|
36
|
-
with open(
|
|
37
|
-
filesize = os.path.getsize(
|
|
40
|
+
with open(video_name, 'rb') as f:
|
|
41
|
+
filesize = os.path.getsize(video_name)
|
|
38
42
|
filehash = filesize
|
|
39
43
|
if filesize < 65536 * 2:
|
|
40
44
|
return None
|