Skip to content

Commit

Permalink
Use three-valued hearing_impaired and foreign_only
Browse files Browse the repository at this point in the history
  • Loading branch information
getzze committed Oct 3, 2024
1 parent 641b11a commit 776f72f
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 27 deletions.
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ extend-ignore-re = [
"(?Rm)^.*#\\s*spellchecker:\\s*disable-line$",
"#\\s*spellchecker:off\\s*\\n.*\\n\\s*#\\s*spellchecker:on"
]
[tool.typos.default.extend-words]
fo = "fo"
[tool.typos.default.extend-identifiers]
tha = "tha"
bre = "bre"
47 changes: 38 additions & 9 deletions subliminal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import tomli
from babelfish import Error as BabelfishError # type: ignore[import-untyped]
from babelfish import Language
from click_option_group import OptionGroup
from click_option_group import MutuallyExclusiveOptionGroup, OptionGroup
from dogpile.cache.backends.file import AbstractFileLock
from dogpile.util.readwrite_lock import ReadWriteMutex
from platformdirs import PlatformDirs
Expand All @@ -40,7 +40,6 @@
)
from subliminal.core import ARCHIVE_EXTENSIONS, scan_name, search_external_subtitles
from subliminal.extensions import default_providers, default_refiners
from subliminal.score import match_hearing_impaired
from subliminal.utils import merge_extend_and_ignore_unions

if TYPE_CHECKING:
Expand Down Expand Up @@ -190,6 +189,14 @@ def plural(quantity: int, name: str, *, bold: bool = True, **kwargs: Any) -> str

providers_config = OptionGroup('Providers configuration')
refiners_config = OptionGroup('Refiners configuration')
hearing_impaired_group = MutuallyExclusiveOptionGroup(
'Hearing impaired subtitles',
help='Require or avoid hearing impaired subtitles. Set to empty if no preference (default).',
)
foreign_only_group = MutuallyExclusiveOptionGroup(
'Foreign only subtitles',
help='Require or avoid foreign-only/forced subtitles. Set to empty if no preference (default).',
)


@click.group(
Expand Down Expand Up @@ -411,7 +418,16 @@ def cache(ctx: click.Context, clear_subliminal: bool) -> None:
),
)
@click.option('-f', '--force', is_flag=True, default=False, help='Force download even if a subtitle already exist.')
@click.option('-hi', '--hearing-impaired', is_flag=True, default=False, help='Prefer hearing impaired subtitles.')
@hearing_impaired_group.option('-hi', '--hearing-impaired', is_flag=True, default=False)
@hearing_impaired_group.option('-HI', '--no-hearing-impaired', is_flag=True, default=False)
@foreign_only_group.option('-fo', '--foreign-only', is_flag=True, default=False)
@foreign_only_group.option('-FO', '--no-foreign-only', is_flag=True, default=False)
@click.option(
'--forced',
is_flag=True,
default=False,
help='Require or avoid forced/foreign-only subtitles. Set to empty if no preference (default).',
)
@click.option(
'-m',
'--min-score',
Expand Down Expand Up @@ -469,6 +485,9 @@ def download(
single: bool,
force: bool,
hearing_impaired: bool,
no_hearing_impaired: bool,
foreign_only: bool,
no_foreign_only: bool,
min_score: int,
language_type_suffix: bool,
language_format: str,
Expand Down Expand Up @@ -496,6 +515,18 @@ def download(
elif encoding is None:
encoding = 'utf-8'

# language_type
hearing_impaired_flag: bool | None = None
if hearing_impaired:
hearing_impaired_flag = True
elif no_hearing_impaired:
hearing_impaired_flag = False
foreign_only_flag: bool | None = None
if foreign_only:
foreign_only_flag = True
elif no_foreign_only:
foreign_only_flag = False

debug = obj.get('debug', False)
if debug:
verbose = 3
Expand Down Expand Up @@ -649,7 +680,8 @@ def download(
v,
language_set,
min_score=scores['hash'] * min_score // 100,
hearing_impaired=hearing_impaired,
hearing_impaired=hearing_impaired_flag,
foreign_only=foreign_only_flag,
only_one=single,
ignore_subtitles=ignore_subtitles,
)
Expand Down Expand Up @@ -701,11 +733,8 @@ def download(
else:
score_color = 'green'

# scale score from 0 to 100 taking out preferences
scaled_score = score
if match_hearing_impaired(s, hearing_impaired=hearing_impaired):
scaled_score -= scores['hearing_impaired']
scaled_score *= 100 / scores['hash']
# scale score from 0 to 100
scaled_score = score * 100 / scores['hash']

# echo some nice colored output
language_str = (
Expand Down
24 changes: 17 additions & 7 deletions subliminal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
refiner_manager,
)
from .score import compute_score as default_compute_score
from .subtitle import SUBTITLE_EXTENSIONS, Subtitle
from .subtitle import SUBTITLE_EXTENSIONS, LanguageType, Subtitle
from .utils import get_age, handle_exception
from .video import VIDEO_EXTENSIONS, Episode, Movie, Video

Expand Down Expand Up @@ -220,7 +220,8 @@ def download_best_subtitles(
languages: Set[Language],
*,
min_score: int = 0,
hearing_impaired: bool = False,
hearing_impaired: bool | None = None,
foreign_only: bool | None = None,
only_one: bool = False,
compute_score: ComputeScore | None = None,
ignore_subtitles: Sequence[str] | None = None,
Expand All @@ -234,10 +235,11 @@ def download_best_subtitles(
:param languages: languages to download.
:type languages: set of :class:`~babelfish.language.Language`
:param int min_score: minimum score for a subtitle to be downloaded.
:param bool hearing_impaired: hearing impaired preference.
:param (bool | None) hearing_impaired: hearing impaired preference (yes/no/indifferent).
:param (bool | None) foreign_only: foreign only preference (yes/no/indifferent).
:param bool only_one: download only one subtitle, not one per language.
:param compute_score: function that takes `subtitle` and `video` as positional arguments,
`hearing_impaired` as keyword argument and returns the score.
and returns the score.
:param ignore_subtitles: list of subtitle ids to ignore (None defaults to an empty list).
:return: downloaded subtitles.
:rtype: list of :class:`~subliminal.subtitle.Subtitle`
Expand All @@ -249,9 +251,14 @@ def download_best_subtitles(
# ignore subtitles
subtitles = [s for s in subtitles if s.id not in ignore_subtitles]

# filter by hearing impaired and foreign only
lt = LanguageType.from_flags(hearing_impaired=hearing_impaired, forced=foreign_only)
if lt != LanguageType.UNKNOWN:
subtitles = [s for s in subtitles if s.language_type == lt]

# sort subtitles by score
scored_subtitles = sorted(
[(s, compute_score(s, video, hearing_impaired=hearing_impaired)) for s in subtitles],
[(s, compute_score(s, video)) for s in subtitles],
key=operator.itemgetter(1),
reverse=True,
)
Expand Down Expand Up @@ -775,7 +782,8 @@ def download_best_subtitles(
languages: Set[Language],
*,
min_score: int = 0,
hearing_impaired: bool = False,
hearing_impaired: bool | None = None,
foreign_only: bool | None = None,
only_one: bool = False,
compute_score: ComputeScore | None = None,
pool_class: type[ProviderPool] = ProviderPool,
Expand All @@ -790,7 +798,8 @@ def download_best_subtitles(
:param languages: languages to download.
:type languages: set of :class:`~babelfish.language.Language`
:param int min_score: minimum score for a subtitle to be downloaded.
:param bool hearing_impaired: hearing impaired preference.
:param (bool | None) hearing_impaired: hearing impaired preference (yes/no/indifferent).
:param (bool | None) foreign_only: foreign only preference (yes/no/indifferent).
:param bool only_one: download only one subtitle, not one per language.
:param compute_score: function that takes `subtitle` and `video` as positional arguments,
`hearing_impaired` as keyword argument and returns the score.
Expand Down Expand Up @@ -825,6 +834,7 @@ def download_best_subtitles(
languages,
min_score=min_score,
hearing_impaired=hearing_impaired,
foreign_only=foreign_only,
only_one=only_one,
compute_score=compute_score,
)
Expand Down
16 changes: 5 additions & 11 deletions subliminal/score.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
class ComputeScore(Protocol):
"""Compute the score of a subtitle matching a video."""

def __call__(self, subtitle: Subtitle, video: Video, *, hearing_impaired: bool | None) -> int: ... # noqa: D102
def __call__(self, subtitle: Subtitle, video: Video) -> int: ... # noqa: D102


# Check if sympy is installed (for tests)
Expand Down Expand Up @@ -141,8 +141,8 @@ def match_hearing_impaired(subtitle: Subtitle, *, hearing_impaired: bool | None
)


def compute_score(subtitle: Subtitle, video: Video, *, hearing_impaired: bool | None = None) -> int:
"""Compute the score of the `subtitle` against the `video` with `hearing_impaired` preference.
def compute_score(subtitle: Subtitle, video: Video) -> int:
"""Compute the score of the `subtitle` against the `video`.
:func:`compute_score` uses the :meth:`Subtitle.get_matches <subliminal.subtitle.Subtitle.get_matches>` method and
applies the scores (either from :data:`episode_scores` or :data:`movie_scores`) after some processing.
Expand All @@ -151,12 +151,11 @@ def compute_score(subtitle: Subtitle, video: Video, *, hearing_impaired: bool |
:type subtitle: :class:`~subliminal.subtitle.Subtitle`
:param video: the video to compute the score against.
:type video: :class:`~subliminal.video.Video`
:param (bool | None) hearing_impaired: hearing impaired preference (None if no preference).
:return: score of the subtitle.
:rtype: int
"""
logger.info('Computing score of %r for video %r with %r', subtitle, video, {'hearing_impaired': hearing_impaired})
logger.info('Computing score of %r for video %r', subtitle, video)

# get the scores dict
scores = get_scores(video)
Expand Down Expand Up @@ -193,17 +192,12 @@ def compute_score(subtitle: Subtitle, video: Video, *, hearing_impaired: bool |
logger.debug('Adding imdb_id match equivalents')
matches |= {'title', 'year', 'country'}

# handle hearing impaired
if match_hearing_impaired(subtitle, hearing_impaired=hearing_impaired):
logger.debug('Matched hearing_impaired')
matches.add('hearing_impaired')

# compute the score
score = int(sum(scores.get(match, 0) for match in matches))
logger.info('Computed score %r with final matches %r', score, matches)

# ensure score is within valid bounds
max_score = scores['hash'] + scores['hearing_impaired']
max_score = scores['hash']
if not (0 <= score <= max_score): # pragma: no cover
logger.info('Clip score between 0 and %d: %d', max_score, score)
score = int(clip(score, 0, max_score))
Expand Down

0 comments on commit 776f72f

Please sign in to comment.