Compare commits

..

17 Commits

Author SHA1 Message Date
Antoine Bertin e7f89c1a19 Release 0.7.2 2013-11-10 11:06:46 +01:00
Antoine Bertin be4f9d92eb Remove unused import 2013-11-10 11:03:37 +01:00
Antoine Bertin ca63b97e79 Parse IETF language format in cli 2013-11-10 10:25:00 +01:00
Antoine Bertin c1ed4a0232 Fix exception handling when validating subtitle 2013-11-10 10:24:23 +01:00
Antoine Bertin b826a0bf08 Use debug level for subtitle track detection 2013-11-10 10:23:41 +01:00
Antoine Bertin fd0d87d719 Use info level when skipping providers 2013-11-10 10:23:24 +01:00
Antoine Bertin 094373f3c1 Add podnapisi provider 2013-11-10 10:22:33 +01:00
Antoine Bertin 5dac623c9f Update to babelfish 0.3.0 2013-11-09 20:25:39 +01:00
Antoine Bertin 57d1e772ec Use more list comprehension 2013-11-09 18:09:19 +01:00
Antoine Bertin 983efbfd9b Reduce debug logging in opensubtitles 2013-11-09 18:06:52 +01:00
Antoine Bertin 1f11e293c1 Add missing docstring 2013-11-09 18:06:17 +01:00
Antoine Bertin bfd278ae1c Change Subtitle repr language format 2013-11-09 18:05:48 +01:00
Antoine Bertin dbe1b9d2af Update guessit requirement 2013-11-09 03:04:13 +01:00
Antoine Bertin d71bc4bf09 Set CLI default cache expiration time to 30 days 2013-11-07 00:33:38 +01:00
Antoine Bertin faf2e1dfa4 Add a CACHE_VERSION to force cache reloading on version change 2013-11-07 00:26:06 +01:00
Antoine Bertin 93360aa1bb Fix find_show_id in tvsubtitles for ambiguous series 2013-11-06 21:08:09 +01:00
Antoine Bertin 8df7780ef9 Switch to 0.7.2 2013-11-06 00:42:44 +01:00
19 changed files with 356 additions and 44 deletions
+13
View File
@@ -1,6 +1,19 @@
Changelog
=========
0.7.2
-----
**release date:** 2013-11-10
* Fix TVSubtitles for ambiguous series
* Add a CACHE_VERSION to force cache reloading on version change
* Set CLI default cache expiration time to 30 days
* Add podnapisi provider
* Support script for languages e.g. Latn, Cyrl
* Improve logging levels
* Fix subtitle validation in some rare cases
0.7.1
-----
**release date:** 2013-11-06
+6 -2
View File
@@ -2,6 +2,10 @@ Cache
=====
.. module:: subliminal.cache
.. autodata:: region
.. autodata:: CACHE_VERSION
.. autofunction:: subliminal_key_generator
.. data:: region
Refer to `dogpile.cache's documentation <http://dogpilecache.readthedocs.org>`_ to see how to configure a region
The dogpile.cache region
Refer to `dogpile.cache's documentation <http://dogpilecache.readthedocs.org>`_ to see how to configure the region
+2 -2
View File
@@ -1,9 +1,9 @@
beautifulsoup4>=4.3.2
guessit>=0.6.1
guessit>=0.6.2
requests>=2.0.1
enzyme>=0.4.0
html5lib>=0.99
dogpile.cache>=0.5.1
babelfish>=0.2.1
babelfish>=0.3.0
charade>=1.0.3
pysrt>=0.5.0
+3 -1
View File
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
setup(name='subliminal',
version='0.7.1',
version='0.7.2',
license='MIT',
description='Subtitles, faster than your thoughts',
long_description=open('README.rst').read() + '\n\n' + open('HISTORY.rst').read(),
@@ -29,9 +29,11 @@ setup(name='subliminal',
'subliminal.providers': ['addic7ed = subliminal.providers.addic7ed:Addic7edProvider',
'bierdopje = subliminal.providers.bierdopje:BierDopjeProvider',
'opensubtitles = subliminal.providers.opensubtitles:OpenSubtitlesProvider',
'podnapisi = subliminal.providers.podnapisi:PodnapisiProvider',
'thesubdb = subliminal.providers.thesubdb:TheSubDBProvider',
'tvsubtitles = subliminal.providers.tvsubtitles:TVsubtitlesProvider'],
'babelfish.converters': ['addic7ed = subliminal.converters.addic7ed:Addic7edConverter',
'podnapisi = subliminal.converters.podnapisi:PodnapisiConverter',
'tvsubtitles = subliminal.converters.tvsubtitles:TVsubtitlesConverter']
},
install_requires=open('requirements.txt').readlines(),
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
__title__ = 'subliminal'
__version__ = '0.7.1'
__version__ = '0.7.2'
__author__ = 'Antoine Bertin'
__license__ = 'MIT'
__copyright__ = 'Copyright 2013 Antoine Bertin'
+4 -4
View File
@@ -47,11 +47,11 @@ def list_subtitles(videos, languages, providers=None, provider_configs=None):
Provider = provider_entry_point.load()
provider_languages = Provider.languages & languages - subtitle_languages
if not provider_languages:
logger.debug('Skipping provider %r: no language to search for', provider_entry_point.name)
logger.info('Skipping provider %r: no language to search for', provider_entry_point.name)
continue
provider_videos = [v for v in videos if Provider.check(v)]
if not provider_videos:
logger.debug('Skipping provider %r: no video to search for', provider_entry_point.name)
logger.info('Skipping provider %r: no video to search for', provider_entry_point.name)
continue
# list subtitles with the provider
@@ -184,10 +184,10 @@ def download_best_subtitles(videos, languages, providers=None, provider_configs=
continue
Provider = provider_entry_point.load()
if not Provider.languages & languages - subtitle_languages:
logger.debug('Skipping provider %r: no language to search for', provider_entry_point.name)
logger.info('Skipping provider %r: no language to search for', provider_entry_point.name)
continue
if not [v for v in videos if Provider.check(v)]:
logger.debug('Skipping provider %r: no video to search for', provider_entry_point.name)
logger.info('Skipping provider %r: no video to search for', provider_entry_point.name)
continue
provider = Provider(**provider_configs.get(provider_entry_point.name, {}))
try:
+26 -2
View File
@@ -1,6 +1,30 @@
# -*- coding: utf-8 -*-
import inspect
import dogpile.cache
#: The subliminal's dogpile.cache region
region = dogpile.cache.make_region()
#: Subliminal's cache version
CACHE_VERSION = 1
def subliminal_key_generator(namespace, fn, to_str=dogpile.cache.compat.string_type):
"""Add a :data:`CACHE_VERSION` to dogpile.cache's default function_key_generator"""
if namespace is None:
namespace = '%d:%s:%s' % (CACHE_VERSION, fn.__module__, fn.__name__)
else:
namespace = '%d:%s:%s|%s' % (CACHE_VERSION, fn.__module__, fn.__name__, namespace)
args = inspect.getargspec(fn)
has_self = args[0] and args[0][0] in ('self', 'cls')
def generate_key(*args, **kw):
if kw:
raise ValueError('Keyword arguments not supported')
if has_self:
args = args[1:]
return namespace + '|' + ' '.join(map(to_str, args))
return generate_key
#: The dogpile.cache region
region = dogpile.cache.make_region(function_key_generator=subliminal_key_generator)
+5 -4
View File
@@ -29,7 +29,7 @@ def subliminal():
required_arguments_group = parser.add_argument_group('required arguments')
required_arguments_group.add_argument('paths', nargs='+', metavar='PATH', help='path to video file or folder')
required_arguments_group.add_argument('-l', '--languages', nargs='+', required=True, metavar='LANGUAGE',
help='wanted languages as alpha2 code (ISO-639-1)')
help='wanted languages as IETF codes e.g. fr, pt-BR, sr-Cyrl ')
# configuration
configuration_group = parser.add_argument_group('configuration')
@@ -81,9 +81,9 @@ def subliminal():
# parse languages
try:
args.languages = {babelfish.Language.fromalpha2(l) for l in args.languages}
args.languages = {babelfish.Language.fromietf(l) for l in args.languages}
except babelfish.Error:
parser.error('argument -l/--languages: codes are not ISO-639-1: %r' % args.languages)
parser.error('argument -l/--languages: codes are not IETF: %r' % args.languages)
# parse age
if args.age is not None:
@@ -138,7 +138,8 @@ def subliminal():
logging.getLogger('subliminal.api').setLevel(logging.INFO)
# configure cache
cache_region.configure('dogpile.cache.dbm', arguments={'filename': args.cache_file})
cache_region.configure('dogpile.cache.dbm', expiration_time=datetime.timedelta(days=30),
arguments={'filename': args.cache_file})
# scan videos
videos = scan_videos([p for p in args.paths if os.path.exists(p)], subtitles=not args.force,
+14 -12
View File
@@ -6,22 +6,24 @@ from babelfish.converters.name import NameConverter
class Addic7edConverter(NameConverter):
def __init__(self):
super(Addic7edConverter, self).__init__()
self.from_addic7ed = {'Català': ('cat', None), 'Chinese (Simplified)': ('zho', None),
'Chinese (Traditional)': ('zho', None), 'Euskera': ('eus', None),
'Galego': ('glg', None), 'Greek': ('ell', None),
'Malay': ('msa', None), 'Portuguese (Brazilian)': ('por', 'BR'),
'Serbian (Cyrillic)': ('srp', None), 'Serbian (Latin)': ('srp', None),
'Spanish (Latin America)': ('spa', None), 'Spanish (Spain)': ('spa', None)}
self.to_addic7ed = {('cat', None): 'Català', ('zho', None): 'Chinese (Simplified)',
('eus', None): 'Euskera', ('glg', None): 'Galego',
('ell', None): 'Greek', ('msa', None): 'Malay',
('por', 'BR'): 'Portuguese (Brazilian)', ('srp', None): 'Serbian (Cyrillic)'}
self.from_addic7ed = {'Català': ('cat',), 'Chinese (Simplified)': ('zho',), 'Chinese (Traditional)': ('zho',),
'Euskera': ('eus',), 'Galego': ('glg',), 'Greek': ('ell',), 'Malay': ('msa',),
'Portuguese (Brazilian)': ('por', 'BR'), 'Serbian (Cyrillic)': ('srp', None, 'Cyrl'),
'Serbian (Latin)': ('srp',), 'Spanish (Latin America)': ('spa',),
'Spanish (Spain)': ('spa',)}
self.to_addic7ed = {('cat',): 'Català', ('zho',): 'Chinese (Simplified)', ('eus',): 'Euskera',
('glg',): 'Galego', ('ell',): 'Greek', ('msa',): 'Malay',
('por', 'BR'): 'Portuguese (Brazilian)', ('srp', None, 'Cyrl'): 'Serbian (Cyrillic)'}
self.codes |= set(self.from_addic7ed.keys())
def convert(self, alpha3, country=None):
def convert(self, alpha3, country=None, script=None):
if (alpha3, country, script) in self.to_addic7ed:
return self.to_addic7ed[(alpha3, country, script)]
if (alpha3, country) in self.to_addic7ed:
return self.to_addic7ed[(alpha3, country)]
return super(Addic7edConverter, self).convert(alpha3, country)
if (alpha3,) in self.to_addic7ed:
return self.to_addic7ed[(alpha3,)]
return super(Addic7edConverter, self).convert(alpha3, country, script)
def reverse(self, addic7ed):
if addic7ed in self.from_addic7ed:
+33
View File
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from babelfish import ReverseConverter, ConvertError, ReverseError
class PodnapisiConverter(ReverseConverter):
def __init__(self):
super(PodnapisiConverter, self).__init__()
self.from_podnapisi = {2: ('eng',), 28: ('spa',), 26: ('pol',), 36: ('srp',), 1: ('slv',), 38: ('hrv',),
9: ('ita',), 8: ('fra',), 48: ('por', 'BR'), 23: ('nld',), 12: ('ara',), 13: ('ron',),
33: ('bul',), 32: ('por',), 16: ('ell',), 15: ('hun',), 31: ('fin',), 30: ('tur',),
7: ('ces',), 25: ('swe',), 27: ('rus',), 24: ('dan',), 22: ('heb',), 51: ('vie',),
52: ('fas',), 5: ('deu',), 14: ('spa', 'AR'), 54: ('ind',), 47: ('srp', None, 'Cyrl'),
3: ('nor',), 20: ('est',), 10: ('bos',), 17: ('zho',), 37: ('slk',), 35: ('mkd',),
11: ('jpn',), 4: ('kor',), 29: ('sqi',), 6: ('isl',), 19: ('lit',), 46: ('ukr',),
44: ('tha',), 53: ('cat',), 56: ('sin',), 21: ('lav',), 40: ('cmn',), 55: ('msa',),
42: ('hin',), 50: ('bel',)}
self.to_podnapisi = {v: k for k, v in self.from_podnapisi.items()}
self.codes = set(self.from_podnapisi.keys())
def convert(self, alpha3, country=None, script=None):
if (alpha3,) in self.to_podnapisi:
return self.to_podnapisi[(alpha3,)]
if (alpha3, country) in self.to_podnapisi:
return self.to_podnapisi[(alpha3, country)]
if (alpha3, country, script) in self.to_podnapisi:
return self.to_podnapisi[(alpha3, country, script)]
raise ConvertError(alpha3, country, script)
def reverse(self, podnapisi):
if podnapisi not in self.from_podnapisi:
raise ReverseError(podnapisi)
return self.from_podnapisi[podnapisi]
+6 -4
View File
@@ -6,15 +6,17 @@ from babelfish.converters.alpha2 import Alpha2Converter
class TVsubtitlesConverter(Alpha2Converter):
def __init__(self):
super(TVsubtitlesConverter, self).__init__()
self.from_tvsubtitles = {'br': ('por', 'BR'), 'ua': ('ukr', None), 'gr': ('ell', None), 'cn': ('zho', None),
'jp': ('jpn', None), 'cz': ('ces', None)}
self.from_tvsubtitles = {'br': ('por', 'BR'), 'ua': ('ukr',), 'gr': ('ell',), 'cn': ('zho',), 'jp': ('jpn',),
'cz': ('ces',)}
self.to_tvsubtitles = {v: k for k, v in self.from_tvsubtitles}
self.codes |= set(self.from_tvsubtitles.keys())
def convert(self, alpha3, country=None):
def convert(self, alpha3, country=None, script=None):
if (alpha3, country) in self.to_tvsubtitles:
return self.to_tvsubtitles[(alpha3, country)]
return super(TVsubtitlesConverter, self).convert(alpha3, country)
if (alpha3,) in self.to_tvsubtitles:
return self.to_tvsubtitles[(alpha3,)]
return super(TVsubtitlesConverter, self).convert(alpha3, country, script)
def reverse(self, tvsubtitles):
if tvsubtitles in self.from_tvsubtitles:
+1 -4
View File
@@ -118,10 +118,7 @@ class BierDopjeProvider(Provider):
result.find('downloadlink').text) for result in root.find('response/results')]
def list_subtitles(self, video, languages):
subtitles = []
for language in languages:
subtitles.extend(self.query(language, video.season, video.episode, video.tvdb_id, video.series))
return subtitles
return [s for l in languages for s in self.query(l, video.season, video.episode, video.tvdb_id, video.series)]
def download_subtitle(self, subtitle):
try:
+2 -2
View File
@@ -128,7 +128,6 @@ class OpenSubtitlesProvider(Provider):
if not response['data']:
logger.debug('No subtitle found')
return []
logger.debug('Found subtitles %r', response['data'])
return [OpenSubtitlesSubtitle(babelfish.Language.fromopensubtitles(r['SubLanguageID']),
bool(int(r['SubHearingImpaired'])), r['IDSubtitleFile'], r['MatchedBy'],
r['MovieKind'], r['MovieHash'], r['MovieName'], r['MovieReleaseName'],
@@ -141,7 +140,8 @@ class OpenSubtitlesProvider(Provider):
query = None
if ('opensubtitles' not in video.hashes or not video.size) and not video.imdb_id:
query = video.name.split(os.sep)[-1]
return self.query(languages, hash=video.hashes.get('opensubtitles'), size=video.size, imdb_id=video.imdb_id, query=query)
return self.query(languages, hash=video.hashes.get('opensubtitles'), size=video.size, imdb_id=video.imdb_id,
query=query)
def download_subtitle(self, subtitle):
try:
+163
View File
@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import io
import logging
import re
import xml.etree.ElementTree
import zipfile
import babelfish
import bs4
import charade
import guessit
import requests
from . import Provider
from .. import __version__
from ..exceptions import InvalidSubtitle, ProviderNotAvailable, ProviderError
from ..subtitle import Subtitle, is_valid_subtitle, compute_guess_matches
from ..video import Episode, Movie
logger = logging.getLogger(__name__)
class PodnapisiSubtitle(Subtitle):
provider_name = 'podnapisi'
def __init__(self, language, id, releases, hearing_impaired, link, series=None, season=None, episode=None,
title=None, year=None):
super(PodnapisiSubtitle, self).__init__(language, hearing_impaired)
self.id = id
self.releases = releases
self.hearing_impaired = hearing_impaired
self.link = link
self.series = series
self.season = season
self.episode = episode
self.title = title
self.year = year
def compute_matches(self, video):
matches = set()
# episode
if isinstance(video, Episode):
# series
if video.series and self.series.lower() == video.series.lower():
matches.add('series')
# season
if video.season and self.season == video.season:
matches.add('season')
# episode
if video.episode and self.episode == video.episode:
matches.add('episode')
# guess
for release in self.releases:
matches |= compute_guess_matches(video, guessit.guess_episode_info(release + '.mkv'))
# movie
elif isinstance(video, Movie):
# title
if video.title and self.title.lower() == video.title.lower():
matches.add('title')
# year
if video.year and self.year == video.year:
matches.add('year')
# guess
for release in self.releases:
matches |= compute_guess_matches(video, guessit.guess_movie_info(release + '.mkv'))
return matches
class PodnapisiProvider(Provider):
languages = {babelfish.Language.frompodnapisi(l) for l in babelfish.CONVERTERS['podnapisi'].codes}
video_types = (Episode, Movie)
server = 'http://simple.podnapisi.net'
link_re = re.compile('^.*(?P<link>/ppodnapisi/download/i/\d+/k/.*$)')
def initialize(self):
self.session = requests.Session()
self.session.headers = {'User-Agent': 'Subliminal/%s' % __version__}
def terminate(self):
self.session.close()
def get(self, url, params=None, is_xml=True):
"""Make a GET request on `url` with the given parameters
:param string url: part of the URL to reach with the leading slash
:param dict params: params of the request
:param bool xml: whether the response content is XML or not
:return: the response
:rtype: :class:`xml.etree.ElementTree.Element` or :class:`bs4.BeautifulSoup`
:raise: :class:`~subliminal.exceptions.ProviderNotAvailable`
"""
try:
r = self.session.get(self.server + '/ppodnapisi' + url, params=params, timeout=10)
except requests.Timeout:
raise ProviderNotAvailable('Timeout after 10 seconds')
if r.status_code != 200:
raise ProviderNotAvailable('Request failed with status code %d' % r.status_code)
if is_xml:
return xml.etree.ElementTree.fromstring(r.content)
else:
return bs4.BeautifulSoup(r.content, ['permissive'])
def query(self, language, series=None, season=None, episode=None, title=None, year=None):
params = {'sXML': 1, 'sJ': language.podnapisi}
if series and season and episode:
params['sK'] = series
params['sTS'] = season
params['sTE'] = episode
elif title:
params['sK'] = title
if year:
params['sY'] = year
else:
raise ValueError('Missing parameters series and season and episode or title')
logger.debug('Searching episode %r', params)
subtitles = []
while True:
root = self.get('/search', params)
if not int(root.find('pagination/results').text):
logger.debug('No subtitle found')
break
if series and season and episode:
subtitles.extend([PodnapisiSubtitle(language, int(s.find('id').text), s.find('release').text.split(),
'h' in (s.find('flags').text or ''), s.find('url').text[38:],
series=series, season=season, episode=episode)
for s in root.findall('subtitle')])
elif title:
subtitles.extend([PodnapisiSubtitle(language, int(s.find('id').text), s.find('release').text.split(),
'h' in (s.find('flags').text or ''), s.find('url').text[38:],
title=title, year=year)
for s in root.findall('subtitle')])
if int(root.find('pagination/current').text) >= int(root.find('pagination/count').text):
break
params['page'] = int(root.find('pagination/current').text) + 1
return subtitles
def list_subtitles(self, video, languages):
if isinstance(video, Episode):
return [s for l in languages for s in self.query(l, series=video.series, season=video.season,
episode=video.episode)]
elif isinstance(video, Movie):
return [s for l in languages for s in self.query(l, title=video.title, year=video.year)]
def download_subtitle(self, subtitle):
soup = self.get(subtitle.link, is_xml=False)
link = soup.find('a', href=self.link_re)
if not link:
raise ProviderError('Cannot find the download link')
try:
r = self.session.get(self.server + self.link_re.match(link['href']).group('link'), timeout=10)
except requests.Timeout:
raise ProviderNotAvailable('Timeout after 10 seconds')
if r.status_code != 200:
raise ProviderNotAvailable('Request failed with status code %d' % r.status_code)
with zipfile.ZipFile(io.BytesIO(r.content)) as zf:
if len(zf.namelist()) > 1:
raise ProviderError('More than one file to unzip')
subtitle_bytes = zf.read(zf.namelist()[0])
subtitle_text = subtitle_bytes.decode(charade.detect(subtitle_bytes)['encoding'], 'replace')
if not is_valid_subtitle(subtitle_text):
raise InvalidSubtitle
return subtitle_text
+11 -2
View File
@@ -61,8 +61,9 @@ class TVsubtitlesProvider(Provider):
'nld', 'pol', 'por', 'ron', 'rus', 'spa', 'swe', 'tur', 'ukr', 'zho']}
video_types = (Episode,)
server = 'http://www.tvsubtitles.net'
episode_id_re = re.compile('^episode-(\d+)\.html$')
subtitle_re = re.compile('^\/subtitle-(\d+)\.html$')
episode_id_re = re.compile('^episode-\d+\.html$')
subtitle_re = re.compile('^\/subtitle-\d+\.html$')
link_re = re.compile('^(?P<series>.+) \(\d{4}-\d{4}\)$')
def initialize(self):
self.session = requests.Session()
@@ -77,6 +78,7 @@ class TVsubtitlesProvider(Provider):
:param string url: part of the URL to reach with the leading slash
:param dict params: params of the request
:param dict data: data of the request
:param string method: method of the request
:return: the response
:rtype: :class:`bs4.BeautifulSoup`
:raise: :class:`~subliminal.exceptions.ProviderNotAvailable`
@@ -106,6 +108,13 @@ class TVsubtitlesProvider(Provider):
if not links:
logger.info('Series %r not found', series)
return None
for link in links:
match = self.link_re.match(link.string)
if not match:
logger.warning('Could not parse %r', link.string)
continue
if match.group('series').lower().replace('.', ' ').strip() == series.lower():
return int(link['href'][8:-5])
return int(links[0]['href'][8:-5])
@region.cache_on_arguments()
+5 -2
View File
@@ -73,7 +73,7 @@ class Subtitle(object):
return score
def __repr__(self):
return '<%s [%r]>' % (self.__class__.__name__, self.language)
return '<%s [%s]>' % (self.__class__.__name__, self.language)
def get_subtitle_path(video_path, language=None):
@@ -106,7 +106,10 @@ def is_valid_subtitle(subtitle_text):
pysrt.from_string(subtitle_text, error_handling=pysrt.ERROR_RAISE)
return True
except pysrt.Error:
return False
pass
except:
logger.exception('Unexpected error when validating subtitle')
return False
def compute_guess_matches(video, guess):
+59
View File
@@ -278,6 +278,54 @@ class OpenSubtitlesProviderTestCase(ProviderTestCase):
self.assertTrue(is_valid_subtitle(subtitle_text))
class PodnapisiProviderTestCase(ProviderTestCase):
provider_name = 'podnapisi'
def test_query_movie_0(self):
video = MOVIES[0]
language = Language('eng')
matches = {frozenset(['video_codec', 'title', 'resolution', 'year']),
frozenset(['title', 'resolution', 'year']),
frozenset(['video_codec', 'title', 'year']),
frozenset(['title', 'year']),
frozenset(['video_codec', 'title', 'resolution', 'release_group', 'year'])}
with self.Provider() as provider:
subtitles = provider.query(language, title=video.title, year=video.year)
self.assertTrue({frozenset(subtitle.compute_matches(video)) for subtitle in subtitles} == matches)
self.assertTrue({subtitle.language for subtitle in subtitles} == {language})
def test_query_episode_0(self):
video = EPISODES[0]
language = Language('eng')
matches = {frozenset(['episode', 'series', 'season', 'video_codec', 'resolution', 'release_group']),
frozenset(['season', 'video_codec', 'episode', 'resolution', 'series'])}
with self.Provider() as provider:
subtitles = provider.query(language, series=video.series, season=video.season, episode=video.episode)
self.assertTrue({frozenset(subtitle.compute_matches(video)) for subtitle in subtitles} == matches)
self.assertTrue({subtitle.language for subtitle in subtitles} == {language})
def test_list_subtitles(self):
video = MOVIES[0]
languages = {Language('eng'), Language('fra')}
matches = {frozenset(['video_codec', 'title', 'resolution', 'year']),
frozenset(['title', 'resolution', 'year']),
frozenset(['video_codec', 'title', 'year']),
frozenset(['title', 'year']),
frozenset(['video_codec', 'title', 'resolution', 'release_group', 'year'])}
with self.Provider() as provider:
subtitles = provider.list_subtitles(video, languages)
self.assertTrue({frozenset(subtitle.compute_matches(video)) for subtitle in subtitles} == matches)
self.assertTrue({subtitle.language for subtitle in subtitles} == languages)
def test_download_subtitle(self):
video = MOVIES[0]
languages = {Language('eng'), Language('fra')}
with self.Provider() as provider:
subtitles = provider.list_subtitles(video, languages)
subtitle_text = provider.download_subtitle(subtitles[0])
self.assertTrue(is_valid_subtitle(subtitle_text))
class TheSubDBProviderTestCase(ProviderTestCase):
provider_name = 'thesubdb'
@@ -325,6 +373,16 @@ class TVsubtitlesProviderTestCase(ProviderTestCase):
show_id = provider.find_show_id('The Big Bang')
self.assertTrue(show_id == 154)
def test_find_show_id_ambiguous(self):
with self.Provider() as provider:
show_id = provider.find_show_id('New Girl')
self.assertTrue(show_id == 977)
def test_find_show_id_no_dots(self):
with self.Provider() as provider:
show_id = provider.find_show_id('Marvel\'s Agents of S H I E L D')
self.assertTrue(show_id == 1340)
def test_find_show_id_error(self):
with self.Provider() as provider:
show_id = provider.find_show_id('the big gaming')
@@ -380,6 +438,7 @@ def suite():
suite.addTest(TestLoader().loadTestsFromTestCase(Addic7edProviderTestCase))
suite.addTest(TestLoader().loadTestsFromTestCase(BierDopjeProviderTestCase))
suite.addTest(TestLoader().loadTestsFromTestCase(OpenSubtitlesProviderTestCase))
suite.addTest(TestLoader().loadTestsFromTestCase(PodnapisiProviderTestCase))
suite.addTest(TestLoader().loadTestsFromTestCase(TheSubDBProviderTestCase))
suite.addTest(TestLoader().loadTestsFromTestCase(TVsubtitlesProviderTestCase))
return suite
+1 -1
View File
@@ -5,7 +5,7 @@ import os
import shutil
from unittest import TestCase, TestSuite, TestLoader, TextTestRunner
from babelfish import Language
from subliminal import list_subtitles, download_subtitles, download_best_subtitles, scan_video, scan_videos
from subliminal import list_subtitles, download_subtitles, download_best_subtitles, scan_video
from subliminal.tests.common import MOVIES, EPISODES
+1 -1
View File
@@ -246,7 +246,7 @@ def scan_video(path, subtitles=True, embedded_subtitles=True):
logger.debug('Found embedded subtitle %r with enzyme', embedded_subtitle_languages)
video.subtitle_languages |= embedded_subtitle_languages
else:
logger.info('MKV has no subtitle track')
logger.debug('MKV has no subtitle track')
except enzyme.Error:
logger.error('Parsing video metadata with enzyme failed')
return video