Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock provider #1185

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1185.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a mock provider to use in doctest. Re-enable doctest
70 changes: 63 additions & 7 deletions docs/user/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,62 @@ Usage
=====
CLI
---

.. testsetup::

from importlib import import_module

from babelfish import Language
from subliminal import provider_manager
from subliminal.providers.mock import mock_subtitle_provider

subtitle_pool = [
{
"language": Language.fromietf('en'),
"subtitle_id": 'ZQo4',
"fake_content": (
b'1\n00:00:04,254 --> 00:00:07,214\n'
b'I\'m gonna run to the store.\nI\'ll pick you up when you\'re done.\n\n'
b'2\n00:00:07,424 --> 00:00:10,968\n'
b'Okay. L like it a little better\nwhen you stay, but all right.\n\n'
b'3\n00:00:11,511 --> 00:00:12,803\n'
b'- Hey, Sheldon.\n- Hello.\n\n'
),
"video_name": 'The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4',
"matches": {'country', 'episode', 'season', 'series', 'video_codec', 'year'},
},
{
"language": Language.fromietf('hu'),
"subtitle_id": 'ZtAW',
"fake_content": (
b'1\n00:00:02,090 --> 00:00:03,970\n'
b'Elszaladok a boltba\nn\xe9h\xe1ny apr\xf3s\xe1g\xe9rt.\n\n'
b'2\n00:00:04,080 --> 00:00:05,550\n'
b'\xc9rted j\xf6v\xf6k, mikor v\xe9gezt\xe9l.\n\n'
b'3\n00:00:05,650 --> 00:00:08,390\n'
b'J\xf3l van. \xc9n jobb szeretem,\nmikor itt maradsz, de j\xf3l van...\n\n'
),
"video_name": 'The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4',
"matches": {'country', 'episode', 'release_group', 'season', 'series', 'source', 'video_codec', 'year'},
},
{
"language": Language.fromietf('hu'),
"subtitle_id": 'ONAW',
"fake_content": (
b'1\n00:00:02,090 --> 00:00:03,970\n'
b'Elszaladok a boltba\nn\xe9h\xe1ny apr\xf3s\xe1g\xe9rt.\n\n'
b'2\n00:00:04,080 --> 00:00:05,550\n'
b'\xc9rted j\xf6v\xf6k, mikor v\xe9gezt\xe9l.\n\n'
),
"video_name": 'The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4',
"matches": {'country', 'episode', 'season', 'series', 'source', 'year'},
},
]

ep = mock_subtitle_provider("Custom", subtitle_pool)
provider_manager.register(ep)


Download English subtitles::

$ subliminal download -l en The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.mp4
Expand Down Expand Up @@ -94,9 +150,9 @@ Listing
To list subtitles, subliminal provides a :func:`~subliminal.core.list_subtitles` function that will return all found
subtitles:

>>> subtitles = list_subtitles([video], {Language('hun')}, providers=['podnapisi'])
>>> subtitles = list_subtitles([video], {Language('hun')}, providers=['custom'])
>>> subtitles[video]
[<PodnapisiSubtitle 'ZtAW' [hu]>, <PodnapisiSubtitle 'ONAW' [hu]>]
[<CustomSubtitle 'ZtAW' [hu]>, <CustomSubtitle 'ONAW' [hu]>]

.. note::

Expand All @@ -118,8 +174,8 @@ And then compute a score with those matches with :func:`~subliminal.score.comput

>>> for s in subtitles[video]:
... {s: compute_score(s, video)}
{<PodnapisiSubtitle 'ZtAW' [hu]>: 789}
{<PodnapisiSubtitle 'ONAW' [hu]>: 772}
{<CustomSubtitle 'ZtAW' [hu]>: 789}
{<CustomSubtitle 'ONAW' [hu]>: 772}

Now you should have a better idea about which one you should choose.

Expand All @@ -145,9 +201,9 @@ Downloading best subtitles
Downloading best subtitles is what you want to do in almost all cases, as a shortcut for listing, scoring and
downloading you can use :func:`~subliminal.core.download_best_subtitles`:

>>> best_subtitles = download_best_subtitles([video], {Language('hun')}, providers=['podnapisi'])
>>> best_subtitles = download_best_subtitles([video], {Language('hun')}, providers=['custom'])
>>> best_subtitles[video]
[<PodnapisiSubtitle 'ZtAW' [hu]>]
[<CustomSubtitle 'ZtAW' [hu]>]
>>> best_subtitle = best_subtitles[video][0]
>>> best_subtitle.content.split(b'\n')[2]
b'Elszaladok a boltba'
Expand All @@ -159,7 +215,7 @@ Save
We got ourselves a nice subtitle, now we can save it on the file system using :func:`~subliminal.core.save_subtitles`:

>>> save_subtitles(video, [best_subtitle])
[<PodnapisiSubtitle 'ZtAW' [hu]>]
[<CustomSubtitle 'ZtAW' [hu]>]
>>> 'The.Big.Bang.Theory.S05E18.HDTV.x264-LOL.hu.srt' in os.listdir()
True

Expand Down
147 changes: 147 additions & 0 deletions subliminal/providers/mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""Mock provider, for testing purposes."""

from __future__ import annotations

import logging
from importlib import import_module
from itertools import count
from typing import TYPE_CHECKING, Any, ClassVar

from babelfish import LANGUAGES, Language # type: ignore[import-untyped]

from subliminal.exceptions import NotInitializedProviderError
from subliminal.matches import guess_matches
from subliminal.subtitle import Subtitle
from subliminal.video import Episode, Movie, Video

from . import Provider

if TYPE_CHECKING:
from collections.abc import Mapping, Sequence, Set

logger = logging.getLogger(__name__)


class MockSubtitle(Subtitle):
"""Mock Subtitle."""

provider_name: ClassVar[str] = 'mock'
_ids: ClassVar = count(0)

fake_content: bytes
video_name: str
matches: set[str]
force_matches: bool

def __init__(
self,
language: Language,
*,
subtitle_id: str = '',
fake_content: bytes = b'',
video_name: str = '',
matches: Set[str] | None = None,
parameters: dict[str, Any] | None = None,
**kwargs: Any,
) -> None:
# generate unique id for mock subtitle
next_id: int = next(self._ids)
if not subtitle_id:
subtitle_id = f'S{next_id:05d}'
super().__init__(
language,
subtitle_id,
**kwargs,
)
self.fake_content = fake_content
self.video_name = video_name
self.force_matches = matches is not None
self.matches = set(matches) if matches is not None else set()
self.parameters = dict(parameters) if parameters is not None else {}

def get_matches(self, video: Video) -> set[str]:
"""Get the matches against the `video`."""
if self.force_matches:
return self.matches
return guess_matches(video, self.parameters)


class MockProvider(Provider):
"""Mock Provider."""

languages: ClassVar[Set[Language]] = {Language(lang) for lang in LANGUAGES}
subtitle_class: ClassVar = MockSubtitle
internal_subtitle_pool: ClassVar[list[MockSubtitle]] = []

video_types: ClassVar = (Episode, Movie)

logged_in: bool
subtitle_pool: list[MockSubtitle]

def __init__(self, subtitle_pool: Sequence[MockSubtitle] | None = None) -> None:
self.logged_in = False
self.subtitle_pool = list(self.internal_subtitle_pool)
if subtitle_pool is not None:
self.subtitle_pool.extend(list(subtitle_pool))

def initialize(self) -> None:
"""Initialize the provider."""
self.logged_in = True

def terminate(self) -> None:
"""Terminate the provider."""
if not self.logged_in:
raise NotInitializedProviderError

self.logged_in = False

def query(
self,
languages: Set[Language],
video: Video | None = None,
matches: Set[str] | None = None,
) -> list[MockSubtitle]:
"""Query the provider for subtitles."""
subtitles = []
for lang in languages:
subtitle = MockSubtitle(language=lang, video=video, matches=matches)
subtitles.append(subtitle)
return subtitles

def list_subtitles(self, video: Video, languages: Set[Language]) -> list[MockSubtitle]:
"""List all the subtitles for the video."""
return [
subtitle
for subtitle in self.subtitle_pool
if subtitle.language in languages and subtitle.video_name == video.name
]

def download_subtitle(self, subtitle: MockSubtitle) -> None:
"""Download the content of the subtitle."""
subtitle.content = subtitle.fake_content


def mock_subtitle_provider(name: str, subtitles_info: Sequence[Mapping[str, Any]]) -> str:
"""Mock a subtitle provider, providing subtitles."""
name_lower = name.lower()
subtitle_class_name = f'{name}Subtitle'
provider_class_name = f'{name}Provider'

# MockSubtitle subclass
MyMockSubtitle = type(subtitle_class_name, (MockSubtitle,), {'provider_name': name_lower})

subtitle_pool = [MyMockSubtitle(**kw) for kw in subtitles_info]

MyMockProvider = type(
provider_class_name,
(MockProvider,),
{
'subtitle_class': MyMockSubtitle,
'internal_subtitle_pool': subtitle_pool,
},
)

mod = import_module('subliminal.providers.mock')
setattr(mod, provider_class_name, MyMockProvider)

return f'{name_lower} = subliminal.providers.mock:{provider_class_name}'
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ allowlist_externals = sphinx-build
commands =
sphinx-build -n -T -W --keep-going -b html -d {envtmpdir}/doctrees docs docs/_build/html
sphinx-build -n -T -W --keep-going -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/html
; sphinx-build -n -T -W --keep-going -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
sphinx-build -n -T -W --keep-going -b doctest -d {envtmpdir}/doctrees docs docs/_build/html


[testenv:changelog]
Expand Down
Loading