Skip to content

Commit

Permalink
Mock provider (#1185)
Browse files Browse the repository at this point in the history
* add mock provider and subtitle

* try testsetup with mock - doctest

* re-enable doctests

* add news
  • Loading branch information
getzze authored Nov 24, 2024
1 parent 2bd5375 commit 49cdee9
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 8 deletions.
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 @@ -95,7 +95,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

0 comments on commit 49cdee9

Please sign in to comment.