Compare commits

..
Author SHA1 Message Date
panni f8f99f0fb2 submod retry; WIP 2019-05-19 06:03:55 +02:00
27 changed files with 240 additions and 620 deletions

No files matched your search

-51
View File
@@ -1,54 +1,3 @@
2.6.5.3109
subscene, addic7ed and titlovi
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
Changelog
- providers: add Napisy24 (polish)
- providers: subscene: reduce provider load by possibly half
- providers: subscene: support logging in (username/password are now required)
- providers: subscene: fallback to non year results if none found with year
2.6.5.3099
subscene, addic7ed and titlovi
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
Changelog
- core: allow system DNS again by putting "system" as the DNS
- providers: subscene: fix again (subscene, contact us please, so we can end this)
2.6.5.3092
subscene, addic7ed and titlovi
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
Changelog
- providers: subscene: fix endpoint (hopefully for longer now)
- providers: subscene: don't search for season packs (broken for now; relieves 50% of server load on provider)
- providers: subscene: don't calculate video fn for now
- providers: argenteam: backport fixes from bazarr
- subtitle: try decoding with utf-16 by default as well (zho/farsi)
- submod: HI: remove music tags by default
- core: compat (bazarr): add env var SZ_KEEP_ENCODING to keep encoding of subtitles
2.6.5.3074
Changelog
- core: cf: bypass cf 95% of the time without captchas
- core: fix breaking line endings of certain languages (chinese, UTF-16); fixes #646
- core: update pysubs2 to 0.2.3
2.6.5.3062
Changelog
- core: cf: optimize
- core: http: don't query DNS with IPs. thanks @fgump (fixes sonarr/radarr)
2.6.5.3041
+1 -5
View File
@@ -119,23 +119,19 @@ def agent_extract_embedded(video_part_map):
for plexapi_part in get_all_parts(plexapi_item):
item_count = item_count + 1
used_one_unknown_stream = False
used_one_known_stream = False
for requested_language in config.lang_list:
skip_unknown = used_one_unknown_stream or used_one_known_stream
embedded_subs = stored_subs.get_by_provider(plexapi_part.id, requested_language, "embedded")
current = stored_subs.get_any(plexapi_part.id, requested_language) or \
requested_language in scanned_video.external_subtitle_languages
if not embedded_subs:
stream_data = get_embedded_subtitle_streams(plexapi_part, requested_language=requested_language,
skip_unknown=skip_unknown)
skip_unknown=used_one_unknown_stream)
if stream_data:
stream = stream_data[0]["stream"]
if stream_data[0]["is_unknown"]:
used_one_unknown_stream = True
else:
used_one_known_stream = True
to_extract.append(({scanned_video: part_info}, plexapi_part, str(stream.index),
str(requested_language), not current))
+2 -11
View File
@@ -761,7 +761,6 @@ class Config(object):
return {'opensubtitles': cast_bool(Prefs['provider.opensubtitles.enabled']),
# 'thesubdb': Prefs['provider.thesubdb.enabled'],
'podnapisi': cast_bool(Prefs['provider.podnapisi.enabled']),
'napisy24': cast_bool(Prefs['provider.napisy24.enabled']),
'titlovi': cast_bool(Prefs['provider.titlovi.enabled']),
'addic7ed': cast_bool(Prefs['provider.addic7ed.enabled']) and self.has_anticaptcha,
'tvsubtitles': cast_bool(Prefs['provider.tvsubtitles.enabled']),
@@ -802,7 +801,6 @@ class Config(object):
providers["argenteam"] = False
providers["assrt"] = False
providers["subscene"] = False
providers["napisy24"] = False
providers_forced_off = dict(providers)
if not self.unrar and providers["legendastv"]:
@@ -866,14 +864,8 @@ class Config(object):
'only_foreign': self.forced_only,
'also_foreign': self.forced_also,
},
'napisy24': {
'username': Prefs['provider.napisy24.username'],
'password': Prefs['provider.napisy24.password'],
},
'subscene': {
'only_foreign': self.forced_only,
'username': Prefs['provider.subscene.username'],
'password': Prefs['provider.subscene.password'],
},
'legendastv': {'username': Prefs['provider.legendastv.username'],
'password': Prefs['provider.legendastv.password'],
@@ -1092,12 +1084,11 @@ class Config(object):
def parse_custom_dns(self):
custom_dns = Prefs['use_custom_dns2'].strip()
os.environ["dns_resolvers"] = ""
if custom_dns and custom_dns != "system":
if custom_dns:
ips = filter(lambda x: x, [d.strip() for d in custom_dns.split(",")])
if ips:
os.environ["dns_resolvers"] = json.dumps(ips)
return os.environ["dns_resolvers"]
return os.environ["dns_resolvers"]
def init_subliminal_patches(self):
# configure custom subtitle destination folders for scanning pre-existing subs
+1 -1
View File
@@ -394,7 +394,7 @@ def get_language_from_stream(lang_code):
return Language.fromietf(lang)
elif lang:
try:
return language_from_stream(lang_code)
return language_from_stream(lang)
except LanguageError:
pass
+10 -24
View File
@@ -177,7 +177,6 @@ def get_all_parts(plex_item):
def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_unknown=True, skip_unknown=False):
streams = []
streams_unknown = []
all_streams = []
has_unknown = False
found_requested_language = False
for stream in part.streams:
@@ -190,40 +189,27 @@ def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_
is_unknown = False
found_requested_language = requested_language and requested_language == language
stream_data = None
if not language:
if not language and config.treat_und_as_first:
# only consider first unknown subtitle stream
if requested_language and config.treat_und_as_first:
if has_unknown and skip_duplicate_unknown:
Log.Debug("skipping duplicate unknown")
continue
if has_unknown and skip_duplicate_unknown:
continue
language = Language.rebuild(list(config.lang_list)[0], forced=is_forced)
else:
language = Language("unk")
language = Language.rebuild(list(config.lang_list)[0], forced=is_forced)
is_unknown = True
has_unknown = True
stream_data = {"stream": stream, "is_unknown": is_unknown, "language": language,
"is_forced": is_forced}
streams_unknown.append(stream_data)
streams_unknown.append({"stream": stream, "is_unknown": is_unknown, "language": language,
"is_forced": is_forced})
if not requested_language or found_requested_language:
stream_data = {"stream": stream, "is_unknown": is_unknown, "language": language,
"is_forced": is_forced}
streams.append(stream_data)
streams.append({"stream": stream, "is_unknown": is_unknown, "language": language,
"is_forced": is_forced})
if found_requested_language:
break
if stream_data:
all_streams.append(stream_data)
if requested_language:
if streams_unknown and not found_requested_language and not skip_unknown:
streams = streams_unknown
else:
streams = all_streams
if streams_unknown and not found_requested_language and not skip_unknown:
streams = streams_unknown
return streams
+1 -35
View File
@@ -335,26 +335,6 @@
"type": "bool",
"default": "true"
},
{
"id": "provider.napisy24.enabled",
"label": "Provider: Enable Napisy24 (pl)",
"type": "bool",
"default": "false"
},
{
"id": "provider.napisy24.username",
"label": "Napisy24 Username",
"type": "text",
"default": ""
},
{
"id": "provider.napisy24.password",
"label": "Napisy24 Password",
"type": "text",
"option": "hidden",
"default": "",
"secure": "true"
},
{
"id": "provider.addic7ed.enabled",
"label": "Provider: Enable Addic7ed (needs AntiCaptcha)",
@@ -451,20 +431,6 @@
"type": "bool",
"default": "true"
},
{
"id": "provider.subscene.username",
"label": "SubScene Username",
"type": "text",
"default": ""
},
{
"id": "provider.subscene.password",
"label": "SubScene Password",
"type": "text",
"option": "hidden",
"default": "",
"secure": "true"
},
{
"id": "provider.supersubtitles.enabled",
"label": "Provider: Enable feliratok.info (Hungarian)",
@@ -895,7 +861,7 @@
},
{
"id": "use_custom_dns2",
"label": "Use custom DNS (IPs, comma-separated, set to 'system' for system DNS. Default: Google/CF)",
"label": "Use custom DNS (IPs, comma-separated, leave empty for system DNS. Default: Google/CF)",
"type": "text",
"default": "1.1.1.1, 8.8.8.8"
},
+3 -3
View File
@@ -13,7 +13,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.6.5.3124</string>
<string>2.6.5.3074</string>
<key>PlexFrameworkVersion</key>
<string>2</string>
<key>PlexPluginClass</key>
@@ -23,7 +23,7 @@
<key>PlexPluginConsoleLogging</key>
<string>0</string>
<key>PlexPluginDevMode</key>
<string>0</string>
<string>1</string>
<key>PlexPluginCodePolicy</key>
<!-- this allows channels to access some python methods which are otherwise blocked, as well as import external code libraries, and interact with the PMS HTTP API -->
<string>Elevated</string>
@@ -32,7 +32,7 @@
&lt;h1&gt;Sub-Zero for Plex&lt;/h1&gt;&lt;i&gt;Subtitles done right&lt;/i&gt;
Version 2.6.5.3124
Version 2.6.5.3074 DEV
Originally based on @bramwalet's awesome &lt;a href=&quot;https://github.com/bramwalet/Subliminal.bundle&quot;&gt;Subliminal.bundle&lt;/a&gt;
@@ -12,13 +12,6 @@ from_subscene = {
'Malay': 'msa', 'Pashto': 'pus', 'Punjabi': 'pan', 'Swahili': 'swa'
}
from_subscene_with_country = {
'Brazillian Portuguese': ('por', 'BR')
}
to_subscene_with_country = {val: key for key, val in from_subscene_with_country.items()}
to_subscene = {v: k for k, v in from_subscene.items()}
exact_languages_alpha3 = [
@@ -41,12 +34,12 @@ language_ids = {
'mkd': 48, 'mal': 64, 'mni': 65, 'mon': 72, 'pus': 67, 'pol': 31,
'por': 32, 'pan': 66, 'rus': 34, 'srp': 35, 'sin': 58, 'slk': 36,
'slv': 37, 'som': 70, 'tgl': 53, 'tam': 59, 'tel': 63, 'tha': 40,
'tur': 41, 'ukr': 56, 'urd': 42, 'yor': 71, 'pt-BR': 4
'tur': 41, 'ukr': 56, 'urd': 42, 'yor': 71
}
# TODO: specify codes for unspecified_languages
unspecified_languages = [
'Big 5 code', 'Bulgarian/ English',
'Big 5 code', 'Brazillian Portuguese', 'Bulgarian/ English',
'Chinese BG code', 'Dutch/ English', 'English/ German',
'Hungarian/ English', 'Rohingya'
]
@@ -57,8 +50,6 @@ alpha3_of_code = {l.name: l.alpha3 for l in supported_languages}
supported_languages.update({Language(l) for l in to_subscene})
supported_languages.update({Language(lang, cr) for lang, cr in to_subscene_with_country})
class SubsceneConverter(LanguageReverseConverter):
codes = {l.name for l in supported_languages}
@@ -70,15 +61,9 @@ class SubsceneConverter(LanguageReverseConverter):
if alpha3 in to_subscene:
return to_subscene[alpha3]
if (alpha3, country) in to_subscene_with_country:
return to_subscene_with_country[(alpha3, country)]
raise ConfigurationError('Unsupported language for subscene: %s, %s, %s' % (alpha3, country, script))
def reverse(self, code):
if code in from_subscene_with_country:
return from_subscene_with_country[code]
if code in from_subscene:
return (from_subscene[code],)
@@ -309,8 +309,7 @@ class SZProviderPool(ProviderPool):
logger.error('Invalid subtitle')
return False
if not os.environ.get("SZ_KEEP_ENCODING", False):
subtitle.normalize()
subtitle.normalize()
return True
@@ -473,7 +472,7 @@ if is_windows_special_path:
SZAsyncProviderPool = SZProviderPool
def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, skip_hashing=False, hash_from=None):
def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, skip_hashing=False):
"""Scan a video from a `path`.
patch:
@@ -538,34 +537,28 @@ def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, ski
video.alternative_titles.append(alt_guess["title"])
logger.debug("Adding alternative title: %s", alt_guess["title"])
if dont_use_actual_file and not hash_from:
if dont_use_actual_file:
return video
# size and hashes
if not skip_hashing:
hash_path = hash_from or path
video.size = os.path.getsize(hash_path)
video.size = os.path.getsize(path)
if video.size > 10485760:
logger.debug('Size is %d', video.size)
osub_hash = None
if "opensubtitles" in providers:
video.hashes['opensubtitles'] = osub_hash = hash_opensubtitles(hash_path)
video.hashes['opensubtitles'] = hash_opensubtitles(path)
if "shooter" in providers:
video.hashes['shooter'] = hash_shooter(hash_path)
video.hashes['shooter'] = hash_shooter(path)
if "thesubdb" in providers:
video.hashes['thesubdb'] = hash_thesubdb(hash_path)
video.hashes['thesubdb'] = hash_thesubdb(path)
if "napiprojekt" in providers:
try:
video.hashes['napiprojekt'] = hash_napiprojekt(hash_path)
video.hashes['napiprojekt'] = hash_napiprojekt(path)
except MemoryError:
logger.warning(u"Couldn't compute napiprojekt hash for %s", hash_path)
if "napisy24" in providers:
# Napisy24 uses the same hash as opensubtitles
video.hashes['napisy24'] = osub_hash or hash_opensubtitles(hash_path)
logger.warning(u"Couldn't compute napiprojekt hash for %s", path)
logger.debug('Computed hashes %r', video.hashes)
else:
@@ -581,7 +574,7 @@ def _search_external_subtitles(path, languages=None, only_one=False, scandir_gen
subtitles = {}
_scandir = _scandir_generic if scandir_generic else scandir
for entry in _scandir(dirpath):
if (not entry.name or entry.name in ('\x0c', '$', ',', '\x7f')) and not scandir_generic:
if not entry.name and not scandir_generic:
logger.debug('Could not determine the name of the file, retrying with scandir_generic')
return _search_external_subtitles(path, languages, only_one, True)
if not entry.is_file(follow_symlinks=False):
@@ -354,6 +354,7 @@ def patch_create_connection():
return _orig_create_connection((ip, port), *args, **kwargs)
except dns.exception.DNSException:
logger.warning("DNS: Couldn't resolve %s with DNS: %s", host, custom_resolver.nameservers)
raise
logger.debug("DNS: Falling back to default DNS or IP on %s", host)
return _orig_create_connection((host, port), *args, **kwargs)
@@ -23,10 +23,9 @@ class ArgenteamSubtitle(Subtitle):
hearing_impaired_verifiable = False
_release_info = None
def __init__(self, language, page_link, download_link, movie_kind, title, season, episode, year, release, version, source,
def __init__(self, language, download_link, movie_kind, title, season, episode, year, release, version, source,
video_codec, tvdb_id, imdb_id, asked_for_episode=None, asked_for_release_group=None, *args, **kwargs):
super(ArgenteamSubtitle, self).__init__(language, page_link=page_link, *args, **kwargs)
self.page_link = page_link
super(ArgenteamSubtitle, self).__init__(language, download_link, *args, **kwargs)
self.download_link = download_link
self.movie_kind = movie_kind
self.title = title
@@ -136,8 +135,7 @@ class ArgenteamProvider(Provider, ProviderSubtitleArchiveMixin):
provider_name = 'argenteam'
languages = {Language.fromalpha2(l) for l in ['es']}
video_types = (Episode, Movie)
BASE_URL = "http://www.argenteam.net/"
API_URL = BASE_URL + "api/v1/"
API_URL = "http://argenteam.net/api/v1/"
subtitle_class = ArgenteamSubtitle
hearing_impaired_verifiable = False
language_list = list(languages)
@@ -242,13 +240,12 @@ class ArgenteamProvider(Provider, ProviderSubtitleArchiveMixin):
for r in content['releases']:
for s in r['subtitles']:
movie_kind = "episode" if is_episode else "movie"
page_link = self.BASE_URL + movie_kind + "/" + str(aid)
sub = ArgenteamSubtitle(language, page_link, s['uri'], movie_kind, returned_title,
sub = ArgenteamSubtitle(language, s['uri'], "episode" if is_episode else "movie", returned_title,
season, episode, year, r.get('team'), r.get('tags'),
r.get('source'), r.get('codec'), content.get("tvdb"), imdb_id,
asked_for_release_group=video.release_group,
asked_for_episode=episode)
asked_for_episode=episode
)
subtitles.append(sub)
if has_multiple_ids:
@@ -1,124 +0,0 @@
import logging
import os
from io import BytesIO
from zipfile import ZipFile
from requests import Session
from subliminal_patch.subtitle import Subtitle
from subliminal_patch.providers import Provider
from subliminal import __short_version__
from subliminal.exceptions import AuthenticationError, ConfigurationError
from subliminal.subtitle import fix_line_ending
from subzero.language import Language
logger = logging.getLogger(__name__)
class Napisy24Subtitle(Subtitle):
'''Napisy24 Subtitle.'''
provider_name = 'napisy24'
def __init__(self, language, hash, imdb_id, napis_id):
super(Napisy24Subtitle, self).__init__(language)
self.hash = hash
self.imdb_id = imdb_id
self.napis_id = napis_id
@property
def id(self):
return self.hash
def get_matches(self, video):
matches = set()
# hash
if 'napisy24' in video.hashes and video.hashes['napisy24'] == self.hash:
matches.add('hash')
# imdb_id
if video.imdb_id and self.imdb_id == video.imdb_id:
matches.add('imdb_id')
return matches
class Napisy24Provider(Provider):
'''Napisy24 Provider.'''
languages = {Language(l) for l in ['pol']}
required_hash = 'napisy24'
api_url = 'http://napisy24.pl/run/CheckSubAgent.php'
def __init__(self, username=None, password=None):
if all((username, password)):
self.username = username
self.password = password
else:
self.username = 'subliminal'
self.password = 'lanimilbus'
self.session = None
def initialize(self):
self.session = Session()
self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__
self.session.headers['Content-Type'] = 'application/x-www-form-urlencoded'
def terminate(self):
self.session.close()
def query(self, language, size, name, hash):
params = {
'postAction': 'CheckSub',
'ua': self.username,
'ap': self.password,
'fs': size,
'fh': hash,
'fn': os.path.basename(name),
'n24pref': 1
}
response = self.session.post(self.api_url, data=params, timeout=10)
response.raise_for_status()
response_content = response.content.split(b'||', 1)
n24_data = response_content[0].decode()
if n24_data[:2] != 'OK':
if n24_data[:11] == 'login error':
raise AuthenticationError('Login failed')
logger.error('Unknown response: %s', response.content)
return None
n24_status = n24_data[:4]
if n24_status == 'OK-0':
logger.info('No subtitles found')
return None
subtitle_info = dict(p.split(':', 1) for p in n24_data.split('|')[1:])
logger.debug('Subtitle info: %s', subtitle_info)
if n24_status == 'OK-1':
logger.info('No subtitles found but got video info')
return None
elif n24_status == 'OK-2':
logger.info('Found subtitles')
elif n24_status == 'OK-3':
logger.info('Found subtitles but not from Napisy24 database')
return None
subtitle_content = response_content[1]
subtitle = Napisy24Subtitle(language, hash, 'tt%s' % subtitle_info['imdb'].zfill(7), subtitle_info['napisId'])
with ZipFile(BytesIO(subtitle_content)) as zf:
subtitle.content = fix_line_ending(zf.open(zf.namelist()[0]).read())
return subtitle
def list_subtitles(self, video, languages):
subtitles = [self.query(l, video.size, video.name, video.hashes['napisy24']) for l in languages]
return [s for s in subtitles if s is not None]
def download_subtitle(self, subtitle):
# there is no download step, content is already filled from listing subtitles
pass
@@ -4,34 +4,24 @@ import io
import logging
import os
import time
import traceback
import requests
import inflect
import re
import json
import HTMLParser
import urlparse
from zipfile import ZipFile
from babelfish import language_converters
from guessit import guessit
from dogpile.cache.api import NO_VALUE
from subliminal import Episode, ProviderError
from subliminal.exceptions import ConfigurationError, ServiceUnavailable
from subliminal.utils import sanitize_release_group
from subliminal.cache import region
from subliminal_patch.http import RetryingCFSession
from subliminal_patch.providers import Provider
from subliminal_patch.providers.mixins import ProviderSubtitleArchiveMixin
from subliminal_patch.subtitle import Subtitle, guess_matches
from subliminal_patch.converters.subscene import language_ids, supported_languages
from subscene_api.subscene import search, Subtitle as APISubtitle, SITE_DOMAIN
from subscene_api.subscene import search, Subtitle as APISubtitle
from subzero.language import Language
p = inflect.engine()
language_converters.register('subscene = subliminal_patch.converters.subscene:SubsceneConverter')
logger = logging.getLogger(__name__)
@@ -122,106 +112,28 @@ class SubsceneProvider(Provider, ProviderSubtitleArchiveMixin):
skip_wrong_fps = False
hearing_impaired_verifiable = True
only_foreign = False
username = None
password = None
search_throttle = 8 # seconds
def __init__(self, only_foreign=False, username=None, password=None):
if not all((username, password)):
raise ConfigurationError('Username and password must be specified')
search_throttle = 2 # seconds
def __init__(self, only_foreign=False):
self.only_foreign = only_foreign
self.username = username
self.password = password
def initialize(self):
logger.info("Creating session")
self.session = RetryingCFSession()
prev_cookies = region.get("subscene_cookies2")
if prev_cookies != NO_VALUE:
logger.debug("Re-using old subscene cookies: %r", prev_cookies)
self.session.cookies.update(prev_cookies)
else:
logger.debug("Logging in")
self.login()
def login(self):
r = self.session.get("https://subscene.com/account/login")
if "Server Error" in r.content:
logger.error("Login unavailable; Maintenance?")
raise ServiceUnavailable("Login unavailable; Maintenance?")
match = re.search(r"<script id='modelJson' type='application/json'>\s*(.+)\s*</script>", r.content)
if match:
h = HTMLParser.HTMLParser()
data = json.loads(h.unescape(match.group(1)))
login_url = urlparse.urljoin(data["siteUrl"], data["loginUrl"])
time.sleep(1.0)
r = self.session.post(login_url,
{
"username": self.username,
"password": self.password,
data["antiForgery"]["name"]: data["antiForgery"]["value"]
})
pep_content = re.search(r"<form method=\"post\" action=\"https://subscene\.com/\">"
r".+name=\"id_token\".+?value=\"(?P<id_token>.+?)\".*?"
r"access_token\".+?value=\"(?P<access_token>.+?)\".+?"
r"token_type.+?value=\"(?P<token_type>.+?)\".+?"
r"expires_in.+?value=\"(?P<expires_in>.+?)\".+?"
r"scope.+?value=\"(?P<scope>.+?)\".+?"
r"state.+?value=\"(?P<state>.+?)\".+?"
r"session_state.+?value=\"(?P<session_state>.+?)\"",
r.content, re.MULTILINE | re.DOTALL)
if pep_content:
r = self.session.post(SITE_DOMAIN, pep_content.groupdict())
try:
r.raise_for_status()
except Exception:
raise ProviderError("Something went wrong when trying to log in: %s", traceback.format_exc())
else:
cj = self.session.cookies.copy()
store_cks = ("scene", "idsrv", "idsrv.xsrf", "idsvr.clients", "idsvr.session", "idsvr.username")
for cn in self.session.cookies.iterkeys():
if cn not in store_cks:
del cj[cn]
logger.debug("Storing cookies: %r", cj)
region.set("subscene_cookies2", cj)
return
raise ProviderError("Something went wrong when trying to log in #1")
def terminate(self):
logger.info("Closing session")
self.session.close()
def _create_filters(self, languages):
self.filters = dict(HearingImpaired="2")
acc_filters = self.filters.copy()
if self.only_foreign:
self.filters["ForeignOnly"] = "True"
acc_filters["ForeignOnly"] = self.filters["ForeignOnly"].lower()
logger.info("Only searching for foreign/forced subtitles")
selected_ids = []
for l in languages:
lid = language_ids.get(l.basename, language_ids.get(l.alpha3, None))
if lid:
selected_ids.append(str(lid))
acc_filters["SelectedIds"] = selected_ids
self.filters["LanguageFilter"] = ",".join(acc_filters["SelectedIds"])
last_filters = region.get("subscene_filters")
if last_filters != acc_filters:
region.set("subscene_filters", acc_filters)
logger.debug("Setting account filters to %r", acc_filters)
self.session.post("https://u.subscene.com/filter", acc_filters, allow_redirects=False)
self.filters["LanguageFilter"] = ",".join((str(language_ids[l.alpha3]) for l in languages
if l.alpha3 in language_ids))
logger.debug("Filter created: '%s'" % self.filters)
@@ -264,11 +176,7 @@ class SubsceneProvider(Provider, ProviderSubtitleArchiveMixin):
def parse_results(self, video, film):
subtitles = []
for s in film.subtitles:
try:
subtitle = SubsceneSubtitle.from_api(s)
except NotImplementedError, e:
logger.info(e)
continue
subtitle = SubsceneSubtitle.from_api(s)
subtitle.asked_for_release_group = video.release_group
if isinstance(video, Episode):
subtitle.asked_for_episode = video.episode
@@ -281,16 +189,10 @@ class SubsceneProvider(Provider, ProviderSubtitleArchiveMixin):
return subtitles
def do_search(self, *args, **kwargs):
try:
return search(*args, **kwargs)
except requests.HTTPError:
region.delete("subscene_cookies2")
def query(self, video):
# vfn = get_video_filename(video)
vfn = get_video_filename(video)
subtitles = []
# logger.debug(u"Searching for: %s", vfn)
#logger.debug(u"Searching for: %s", vfn)
# film = search(vfn, session=self.session)
#
# if film and film.subtitles:
@@ -299,17 +201,16 @@ class SubsceneProvider(Provider, ProviderSubtitleArchiveMixin):
# else:
# logger.debug('No release results found')
# time.sleep(self.search_throttle)
#time.sleep(self.search_throttle)
# re-search for episodes without explicit release name
if isinstance(video, Episode):
titles = list(set([video.series] + video.alternative_series))[:2]
# term = u"%s S%02iE%02i" % (video.series, video.season, video.episode)
more_than_one = len(titles) > 1
for series in titles:
#term = u"%s S%02iE%02i" % (video.series, video.season, video.episode)
more_than_one = len([video.series] + video.alternative_series) > 1
for series in [video.series] + video.alternative_series:
term = u"%s - %s Season" % (series, p.number_to_words("%sth" % video.season).capitalize())
logger.debug('Searching for alternative results: %s', term)
film = self.do_search(term, session=self.session, release=False, throttle=self.search_throttle)
film = search(term, session=self.session, release=False, throttle=self.search_throttle)
if film and film.subtitles:
logger.debug('Alternative results found: %s', len(film.subtitles))
subtitles += self.parse_results(video, film)
@@ -317,27 +218,26 @@ class SubsceneProvider(Provider, ProviderSubtitleArchiveMixin):
logger.debug('No alternative results found')
# packs
# if video.season_fully_aired:
# term = u"%s S%02i" % (series, video.season)
# logger.debug('Searching for packs: %s', term)
# time.sleep(self.search_throttle)
# film = search(term, session=self.session, throttle=self.search_throttle)
# if film and film.subtitles:
# logger.debug('Pack results found: %s', len(film.subtitles))
# subtitles += self.parse_results(video, film)
# else:
# logger.debug('No pack results found')
# else:
# logger.debug("Not searching for packs, because the season hasn't fully aired")
if video.season_fully_aired:
term = u"%s S%02i" % (series, video.season)
logger.debug('Searching for packs: %s', term)
time.sleep(self.search_throttle)
film = search(term, session=self.session, throttle=self.search_throttle)
if film and film.subtitles:
logger.debug('Pack results found: %s', len(film.subtitles))
subtitles += self.parse_results(video, film)
else:
logger.debug('No pack results found')
else:
logger.debug("Not searching for packs, because the season hasn't fully aired")
if more_than_one:
time.sleep(self.search_throttle)
else:
titles = list(set([video.title] + video.alternative_titles))[:2]
more_than_one = len(titles) > 1
for title in titles:
logger.debug('Searching for movie results: %r', title)
film = self.do_search(title, year=video.year, session=self.session, limit_to=None, release=False,
throttle=self.search_throttle)
more_than_one = len([video.title] + video.alternative_titles) > 1
for title in [video.title] + video.alternative_titles:
logger.debug('Searching for movie results: %s', title)
film = search(title, year=video.year, session=self.session, limit_to=None, release=False,
throttle=self.search_throttle)
if film and film.subtitles:
subtitles += self.parse_results(video, film)
if more_than_one:
@@ -226,7 +226,7 @@ class TitloviProvider(Provider, ProviderSubtitleArchiveMixin):
page_link = self.server_url + sub.a.attrs['href']
# subtitle language
_lang = sub.select_one('.lang')
match = lang_re.search(_lang.attrs.get('src', _lang.attrs.get('data-cfsrc', '')))
match = lang_re.search(_lang.attrs.get('src', _lang.attrs.get('cfsrc', '')))
if match:
try:
# decode language
@@ -117,14 +117,14 @@ class Subtitle(Subtitle_):
logger.info('Guessing encoding for language %s', self.language)
encodings = ['utf-8']
encodings = ['utf-8', 'utf-16']
# add language-specific encodings
# http://scratchpad.wikia.com/wiki/Character_Encoding_Recommendation_for_Languages
if self.language.alpha3 == 'zho':
encodings.extend(['cp936', 'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp_2', 'cp950', 'gb18030', 'big5',
'big5hkscs', 'utf-16'])
'big5hkscs'])
elif self.language.alpha3 == 'jpn':
encodings.extend(['shift-jis', 'cp932', 'euc_jp', 'iso2022_jp', 'iso2022_jp_1', 'iso2022_jp_2',
'iso2022_jp_2004', 'iso2022_jp_3', 'iso2022_jp_ext', ])
@@ -133,7 +133,7 @@ class Subtitle(Subtitle_):
# arabian/farsi
elif self.language.alpha3 in ('ara', 'fas', 'per'):
encodings.extend(['windows-1256', 'utf-16'])
encodings.append('windows-1256')
elif self.language.alpha3 == 'heb':
encodings.extend(['windows-1255', 'iso-8859-8'])
elif self.language.alpha3 == 'tur':
@@ -251,7 +251,8 @@ class Subtitle(Subtitle_):
subs = pysubs2.SSAFile.from_string(text, fps=self.plex_media_fps)
unicontent = self.pysubs2_to_unicode(subs)
self.content = unicontent.encode(self._guessed_encoding)
self.content = unicontent.encode("utf-8")
self._guessed_encoding = "utf-8"
except:
logger.exception("Couldn't convert subtitle %s to .srt format: %s", self, traceback.format_exc())
return False
@@ -319,8 +320,7 @@ class Subtitle(Subtitle_):
:return: string
"""
if not self.mods:
return fix_text(self.content.decode(encoding=self._guessed_encoding), **ftfy_defaults).encode(
encoding=self._guessed_encoding)
return fix_text(self.content.decode("utf-8"), **ftfy_defaults).encode(encoding="utf-8")
submods = SubtitleModifications(debug=debug)
if submods.load(content=self.text, language=self.language):
@@ -329,7 +329,7 @@ class Subtitle(Subtitle_):
self.mods = submods.mods_used
content = fix_text(self.pysubs2_to_unicode(submods.f, format=format), **ftfy_defaults)\
.encode(encoding=self._guessed_encoding)
.encode(encoding="utf-8")
submods.f = None
del submods
return content
+1 -1
View File
@@ -21,7 +21,7 @@ if debug:
logging.basicConfig(level=logging.DEBUG)
#sub = Subtitle(Language.fromietf("eng:forced"), mods=["common", "remove_HI", "OCR_fixes", "fix_uppercase", "shift_offset(ms=-500)", "shift_offset(ms=500)", "shift_offset(s=2,ms=800)"])
sub = Subtitle(Language.fromietf("eng"), mods=["common", "remove_HI", "OCR_fixes", "fix_uppercase", "shift_offset(ms=0,s=1)"])
sub = Subtitle(Language.fromietf("eng:forced"), mods=["common", "remove_HI", "OCR_fixes", "fix_uppercase", "shift_offset(ms=0,s=1)"])
sub.content = open(fn).read()
sub.normalize()
content = sub.get_modified_content(debug=True)
@@ -30,7 +30,6 @@ import enum
import sys
import requests
import time
import logging
is_PY2 = sys.version_info[0] < 3
if is_PY2:
@@ -40,13 +39,8 @@ else:
from contextlib import suppress
from urllib2.request import Request, urlopen
from dogpile.cache.api import NO_VALUE
from subliminal.cache import region
from bs4 import BeautifulSoup, NavigableString
logger = logging.getLogger(__name__)
# constants
HEADERS = {
}
@@ -56,21 +50,14 @@ DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWeb"\
"Kit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36"
ENDPOINT_RE = re.compile(ur'(?uis)<form.+?action="/subtitles/(.+)">.*?<input type="text"')
class NewEndpoint(Exception):
pass
# utils
def soup_for(url, data=None, session=None, user_agent=DEFAULT_USER_AGENT):
def soup_for(url, session=None, user_agent=DEFAULT_USER_AGENT):
url = re.sub("\s", "+", url)
if not session:
r = Request(url, data=None, headers=dict(HEADERS, **{"User-Agent": user_agent}))
html = urlopen(r).read().decode("utf-8")
else:
ret = session.post(url, data=data)
ret = session.get(url)
ret.raise_for_status()
html = ret.text
return BeautifulSoup(html, "html.parser")
@@ -125,7 +112,7 @@ class Subtitle(object):
subtitles = []
for row in rows:
if row.td.a is not None and row.td.get("class", ["lazy"])[0] != "empty":
if row.td.a is not None:
subtitles.append(cls.from_row(row))
return subtitles
@@ -255,42 +242,29 @@ def get_first_film(soup, section, year=None, session=None):
url = SITE_DOMAIN + t.div.a.get("href")
break
if not url:
# fallback to non-year results
logger.info("Falling back to non-year results as year wasn't found (%s)", year)
url = SITE_DOMAIN + tag.findNext("ul").find("li").div.a.get("href")
return
return Film.from_url(url, session=session)
def find_endpoint(session, content=None):
endpoint = region.get("subscene_endpoint2")
if endpoint is NO_VALUE:
if not content:
content = session.get(SITE_DOMAIN).text
m = ENDPOINT_RE.search(content)
if m:
endpoint = m.group(1).strip()
logger.debug("Switching main endpoint to %s", endpoint)
region.set("subscene_endpoint2", endpoint)
return endpoint
def search(term, release=True, session=None, year=None, limit_to=SearchTypes.Exact, throttle=0):
# note to subscene: if you actually start to randomize the endpoint, we'll have to query your server even more
endpoints = ["searching", "search", "srch", "find"]
if release:
endpoint = "release"
else:
endpoint = find_endpoint(session)
time.sleep(throttle)
endpoints = ["release"]
if not endpoint:
logger.error("Couldn't find endpoint, exiting")
return
soup = soup_for("%s/subtitles/%s" % (SITE_DOMAIN, endpoint), data={"query": term},
session=session)
soup = None
for endpoint in endpoints:
try:
soup = soup_for("%s/subtitles/%s?q=%s" % (SITE_DOMAIN, endpoint, term),
session=session)
except requests.HTTPError, e:
if e.response.status_code == 404:
time.sleep(throttle)
# fixme: detect endpoint from html
continue
raise
break
if soup:
if "Subtitle search by" in str(soup):
@@ -8,25 +8,6 @@ repl_map = {
"dk": "da",
"nld": "nl",
"english": "en",
"alb": "sq",
"arm": "hy",
"baq": "eu",
"bur": "my",
"chi": "zh",
"cze": "cs",
"dut": "nl",
"fre": "fr",
"geo": "ka",
"ger": "de",
"gre": "el",
"ice": "is",
"mac": "mk",
"mao": "mi",
"may": "ms",
"per": "fa",
"rum": "ro",
"slo": "sk",
"tib": "bo",
}
@@ -42,7 +42,7 @@ data = {'bos': {'BeginLines': {'data': OrderedDict(),
'pattern': u"(?um)(?:\\,\\ sin|\\ mothen|\\ can\\'t\\_|\\ openiL|\\ of\\\ufb02|pshycol|\\ i\\.\\.\\.|\\ L\\.)$"},
'PartialLines': {'data': OrderedDict([(u' /be ', u' I be '), (u" aren '1'", u" aren't"), (u" aren'tyou", u" aren't you"), (u" doesn '1'", u" doesn't"), (u" fr/eno'", u' friend'), (u" fr/eno'.", u' friend.'), (u" haven 'z' ", u" haven't "), (u" haven 'z'.", u" haven't."), (u' I ha ve ', u' I have '), (u" I']I ", u" I'll "), (u' L am', u' I am'), (u' L can', u' I can'), (u" L don't ", u" I don't "), (u' L hate ', u' I hate '), (u' L have ', u' I have '), (u' L like ', u' I like '), (u' L will', u' I will'), (u' L would', u' I would'), (u" L'll ", u" I'll "), (u" L've ", u" I've "), (u' m y family', u' my family'), (u" 's ", u"'s "), (u" shou/dn '1 ", u" shouldn't "), (u" won 'z' ", u" won't "), (u" won 'z'.", u" won't."), (u" wou/c/n 'z' ", u" wouldn't "), (u" wou/c/n 'z'.", u" wouldn't."), (u" wou/dn 'z' ", u" wouldn't "), (u" wou/dn 'z'.", u" wouldn't."), (u'/ did', u'I did'), (u'/ have ', u'I have '), (u'/ just ', u'I just '), (u'/ loved ', u'I loved '), (u'/ need', u'I need'), (u'|was11', u'I was 11'), (u'at Hrst', u'at first'), (u"B ullshiz'", u'Bullshit'), (u'big lunk', u'love you'), (u"can 't", u"can't"), (u"can' t ", u"can't "), (u"can 't ", u"can't "), (u'CHA TTERING', u'CHATTERING'), (u'come 0n', u'come on'), (u'Come 0n', u'Come on'), (u"couldn 't", u"couldn't"), (u"couldn' t ", u"couldn't "), (u"couldn 't ", u"couldn't "), (u"Destin y's", u"Destiny's"), (u"didn 't", u"didn't"), (u"didn' t ", u"didn't "), (u"didn 't ", u"didn't "), (u"Doesn '1'", u"Doesn't"), (u"doesn '1' ", u"doesn't "), (u"doesn '1\u2018 ", u"doesn't "), (u"doesn 't", u"doesn't"), (u"doesn'1' ", u"doesn't "), (u"doesn'1\u2018 ", u"doesn't "), (u"don '1' ", u"don't "), (u"don '1\u2018 ", u"don't "), (u"don '2' ", u"don't "), (u" aren '2'", u" aren't"), (u"aren '2' ", u"aren't "), (u"don '2\u2018 ", u"don't "), (u"don 't", u"don't"), (u"Don' t ", u"Don't "), (u"Don 't ", u"Don't "), (u"don'1' ", u"don't "), (u"don'1\u2018 ", u"don't "), (u"there '5 ", u"there's "), (u'E very', u'Every'), (u'get 0n', u'get on'), (u'go 0n', u'go on'), (u'Go 0n', u'Go on'), (u"H3993' birthday", u'Happy birthday'), (u"hadn 't", u"hadn't"), (u"he 's", u"he's"), (u"He 's", u"He's"), (u'He y', u'Hey'), (u'he)/', u'hey'), (u'He)/', u'Hey'), (u'HEA VY', u'HEAVY'), (u'Henry ll', u'Henry II'), (u'Henry lll', u'Henry III'), (u'Henry Vlll', u'Henry VIII'), (u'Henry Vll', u'Henry VII'), (u'Henry Vl', u'Henry VI'), (u'Hold 0n', u'Hold on'), (u'I am. ls', u'I am. Is'), (u'I d0', u'I do'), (u"I 'm", u"I'm"), (u"I 'rn ", u"I'm "), (u"I 've", u"I've"), (u'I0 ve her', u'love her'), (u'I0 ve you', u'love you'), (u"I02'", u'lot'), (u"I'm sony", u"I'm sorry"), (u"isn' t ", u"isn't "), (u"isn 't ", u"isn't "), (u'K)/le', u'Kyle'), (u'L ook', u'Look'), (u'let me 90', u'let me go'), (u'Let me 90', u'Let me go'), (u"let's 90", u"let's go"), (u"Let's 90", u"Let's go"), (u'lfl had', u'If I had'), (u'lova you', u'love you'), (u'Lova you', u'love you'), (u'lovo you', u'love you'), (u'Lovo you', u'love you'), (u'ls anyone', u'Is anyone'), (u'ls he', u'Is he'), (u'-ls he', u'- Is he'), (u'ls it', u'Is it'), (u'-ls it', u'- Is it'), (u'ls she', u'Is she'), (u'-ls she', u'- Is she'), (u'ls that', u'Is that'), (u'-ls that', u'- Is that'), (u'ls this', u'Is this'), (u'-ls this', u'- Is this'), (u'Maze] tov', u'Mazel tov'), (u"N02' ", u'Not '), (u' of 0ur ', u' of our '), (u' ot mine ', u' of mine '), (u'PLA YING', u'PLAYING'), (u'REPEA TING ', u'REPEATING '), (u'Sa y', u'Say'), (u"she 's", u"she's"), (u"She 's", u"She's"), (u"shouldn 't", u"shouldn't"), (u'sta y', u'stay'), (u'Sta y', u'Stay'), (u'SWO rd', u'Sword'), (u'taka care', u'take care'), (u'Taka care', u'Take care'), (u'the Hrst', u'the first'), (u'toc late', u'too late'), (u'uf me', u'of me'), (u'uf our', u'of our'), (u'wa y', u'way'), (u'Wal-I\\/Iart', u'Wal-Mart'), (u"wasn '1' ", u"wasn't "), (u"Wasn '1' ", u"Wasn't "), (u"wasn '1\u2018 ", u"wasn't "), (u"Wasn '1\u2018 ", u"Wasn't "), (u"wasn 't", u"wasn't"), (u"Wasn 't", u"Wasn't"), (u"we 've", u"we've"), (u"We 've", u"We've"), (u"wem' off", u'went off'), (u"weren 't", u"weren't"), (u"who 's", u"who's"), (u"won 't", u"won't"), (u'would ha ve', u'would have '), (u"wouldn 't", u"wouldn't"), (u"Wouldn 't", u"Wouldn't"), (u'y()u', u'you'), (u'you QUYS', u'you guys'), (u"you' re ", u"you're "), (u"you 're ", u"you're "), (u"you 've", u"you've"), (u"You 've", u"You've"), (u"you' ve ", u"you've "), (u"you 've ", u"you've "), (u'aftera while', u'after a while'), (u'Aftera while', u'After a while'), (u'THUN DERCLAPS', u'THUNDERCLAPS'), (u'(BUZZI N G)', u'(BUZZING)'), (u'[BUZZI N G]', u'[BUZZING]'), (u'(G RU NTING', u'(GRUNTING'), (u'[G RU NTING', u'[GRUNTING'), (u'(G ROWLING', u'(GROWLING'), (u'[G ROWLING', u'[GROWLING'), (u' WAI LS)', u'WAILS)'), (u' WAI LS]', u'WAILS]'), (u'(scu RRYING)', u'(SCURRYING)'), (u'[scu RRYING]', u'[SCURRYING]'), (u'(GRUNT5)', u'(GRUNTS)'), (u'[GRUNT5]', u'[GRUNTS]'), (u'NARRA TOR:', u'NARRATOR:'), (u'(GROAN ING', u'(GROANING'), (u'[GROAN ING'Line truncated
'pattern': u"(?um)(?:(?<=\\s)|(?<=^)|(?<=\\b))(?:\\ \\/be\\ |\\ aren\\ \\'1\\'|\\ aren\\'tyou|\\ doesn\\ \\'1\\'|\\ fr\\/eno\\'|\\ fr\\/eno\\'\\.|\\ haven\\ \\'z\\'\\ |\\ haven\\ \\'z\\'\\.|\\ I\\ ha\\ ve\\ |\\ I\\'\\]I\\ |\\ L\\ am|\\ L\\ can|\\ L\\ don\\'t\\ |\\ L\\ hate\\ |\\ L\\ have\\ |\\ L\\ like\\ |\\ L\\ will|\\ L\\ would|\\ L\\'ll\\ |\\ L\\'ve\\ |\\ m\\ y\\ family|\\ \\'s\\ |\\ shou\\/dn\\ \\'1\\ |\\ won\\ \\'z\\'\\ |\\ won\\ \\'z\\'\\.|\\ wou\\/c\\/n\\ \\'z\\'\\ |\\ wou\\/c\\/n\\ \\'z\\'\\.|\\ wou\\/dn\\ \\'z\\'\\ |\\ wou\\/dn\\ \\'z\\'\\.|\\/\\ did|\\/\\ have\\ |\\/\\ just\\ |\\/\\ loved\\ |\\/\\ need|\\|was11|at\\ Hrst|B\\ ullshiz\\'|big\\ lunk|can\\ \\'t|can\\'\\ t\\ |can\\ \\'t\\ |CHA\\ TTERING|come\\ 0n|Come\\ 0n|couldn\\ \\'t|couldn\\'\\ t\\ |couldn\\ \\'t\\ |Destin\\ y\\'s|didn\\ \\'t|didn\\'\\ t\\ |didn\\ \\'t\\ |Doesn\\ \\'1\\'|doesn\\ \\'1\\'\\ |doesn\\ \\'1\\\u2018\\ |doesn\\ \\'t|doesn\\'1\\'\\ |doesn\\'1\\\u2018\\ |don\\ \\'1\\'\\ |don\\ \\'1\\\u2018\\ |don\\ \\'2\\'\\ |\\ aren\\ \\'2\\'|aren\\ \\'2\\'\\ |don\\ \\'2\\\u2018\\ |don\\ \\'t|Don\\'\\ t\\ |Don\\ \\'t\\ |don\\'1\\'\\ |don\\'1\\\u2018\\ |there\\ \\'5\\ |E\\ very|get\\ 0n|go\\ 0n|Go\\ 0n|H3993\\'\\ birthday|hadn\\ \\'t|he\\ \\'s|He\\ \\'s|He\\ y|he\\)\\/|He\\)\\/|HEA\\ VY|Henry\\ ll|Henry\\ lll|Henry\\ Vlll|Henry\\ Vll|Henry\\ Vl|Hold\\ 0n|I\\ am\\.\\ ls|I\\ d0|I\\ \\'m|I\\ \\'rn\\ |I\\ \\'ve|I0\\ ve\\ her|I0\\ ve\\ you|I02\\'|I\\'m\\ sony|isn\\'\\ t\\ |isn\\ \\'t\\ |K\\)\\/le|L\\ ook|let\\ me\\ 90|Let\\ me\\ 90|let\\'s\\ 90|Let\\'s\\ 90|lfl\\ had|lova\\ you|Lova\\ you|lovo\\ you|Lovo\\ you|ls\\ anyone|ls\\ he|\\-ls\\ he|ls\\ it|\\-ls\\ it|ls\\ she|\\-ls\\ she|ls\\ that|\\-ls\\ that|ls\\ this|\\-ls\\ this|Maze\\]\\ tov|N02\\'\\ |\\ of\\ 0ur\\ |\\ ot\\ mine\\ |PLA\\ YING|REPEA\\ TING\\ |Sa\\ y|she\\ \\'s|She\\ \\'s|shouldn\\ \\'t|sta\\ y|Sta\\ y|SWO\\ rd|taka\\ care|Taka\\ care|the\\ Hrst|toc\\ late|uf\\ me|uf\\ our|wa\\ y|Wal\\-I\\\\\\/Iart|wasn\\ \\'1\\'\\ |Wasn\\ \\'1\\'\\ |wasn\\ \\'1\\\u2018\\ |Wasn\\ \\'1\\\u2018\\ |wasn\\ \\'t|Wasn\\ \\'t|we\\ \\'ve|We\\ \\'ve|wem\\'\\ off|weren\\ \\'t|who\\ \\'s|won\\ \\'t|would\\ ha\\ ve|wouldn\\ \\'t|Wouldn\\ \\'t|y\\(\\)u|you\\ QUYS|you\\'\\ re\\ |you\\ \\'re\\ |you\\ \\'ve|You\\ \\'ve|you\\'\\ ve\\ |you\\ \\'ve\\ |aftera\\ while|Aftera\\ while|THUN\\ DERCLAPS|\\(BUZZI\\ N\\ G\\)|\\[BUZZI\\ N\\ G\\]|\\(G\\ RU\\ NTING|\\[G\\ RU\\ NTING|\\(G\\ ROWLING|\\[G\\ ROWLING|\\ WAI\\ LS\\)|\\ WAI\\ LS\\]|\\(scu\\ RRYING\\)|\\[scu\\ RRYING\\]|\\(GRUNT5\\)|\\[GRUNT5\\]|NARRA\\ TOR\\:|\\(GROAN\\ ING|\\[GROAN\\ ING|GROAN\\ ING\\)|GROAN\\ ING\\]|\\(LAUGH\\ ING|\\[LAUGH\\ ING|LAUGH\\ ING\\)|LAUGH\\ ING\\]|\\(BU\\ BBLING|\\[BU\\ BBLING|BU\\ BBLING\\)|BU\\ BBLING\\]|\\(SH\\ USHING|\\[SH\\ USHING|SH\\ USHING\\)|SH\\ USHING\\]|\\(CH\\ ILDREN|\\[CH\\ ILDREN|CH\\ ILDREN\\)|CH\\ ILDREN\\]|\\(MURMU\\ RING|\\[MURMU\\ RING|MURMU\\ RING\\)|MURMU\\ RING\\]|\\(GU\\ N\\ |\\[GU\\ N\\ |GU\\ N\\)|GU\\ N\\]|CH\\ ILDREN\\:|STU\\ DENTS\\:|\\(WH\\ ISTLE|\\[WH\\ ISTLE|WH\\ ISTLE\\)|WH\\ ISTLE\\]|U\\ LU\\ LATING|AU\\ DIENCE\\:|HA\\ WAIIAN|\\(ARTH\\ UR|\\[ARTH\\ UR|ARTH\\ UR\\)|ARTH\\ UR\\]|J\\ EREMY\\:|\\(ELEVA\\ TOR|\\[ELEVA\\ TOR|ELEVA\\ TOR\\)|ELEVA\\ TOR\\]|CONTIN\\ U\\ ES|WIN\\ D\\ HOWLING|telis\\ me|Telis\\ me|\\.\\ Ls\\ |\\!\\ Ls\\ |\\?\\ Ls\\ |\\.\\ Lt\\ |\\!\\ Lt\\ |\\?\\ Lt\\ |SQMEWH\\ ERE\\ ELSE|\\ I\\,m\\ |\\ I\\,ve\\ |\\ you\\,re\\ |\\ you\\,ll\\ |\\ doesn\\,t\\ |\\ let\\,s\\ |\\ he\\,s\\ |\\ it\\,s\\ |\\ can\\,t\\ |\\ Can\\,t\\ |\\ don\\,t\\ |\\ Don\\,t\\ |wouldn\\ \\'tyou|\\ lgot\\ it|\\ you\\,ve\\ |\\ I\\ ve\\ |\\ I\\ ii\\ |\\ I\\ m\\ |\\ why\\ d\\ |\\ couldn\\ t\\ |\\ that\\ s\\ |\\ i\\.\\.\\.\\ |L\\ don\\'t|L\\ won\\'t|L\\ should|L\\ had|L\\ happen|L\\ wasn\\'t|H\\ i|L\\ didn\\'t|L\\ do|L\\ could|L\\ will|L\\ suggest|L\\ reckon|L\\ am|L\\ couldn\\'t|L\\ might|L\\ would|L\\ was|L\\ know|L\\ think|L\\ haven\\'t|L\\ have\\ |L\\ want|L\\ can|L\\ love|L\\ like)(?:(?=\\s)|(?=$)|(?=\\b))"},
'PartialWordsAlways': {'data': OrderedDict([(u'\xa4', u'o'), (u'lVI', u'M'), (u'IVl', u'M'), (u'lVl', u'M'), (u'I\\/I', u'M'), (u'l\\/I', u'M'), (u'I\\/l', u'M'), (u'l\\/l', u'M'), (u'IVIa', u'Ma'), (u'IVIe', u'Me'), (u'IVIi', u'Mi'), (u'IVIo', u'Mo'), (u'IVIu', u'Mu'), (u'IVIy', u'My'), (u' l ', u' I '), (u'l/an', u'lian'), (u'\xb0x\xb0', u'%'), (u'\xc3\xc2s', u"'s"), (u'at/on', u'ation'), (u'lljust', u'll just'), (u"'sjust", u"'s just"), (u'";', u"'s"), (u'compiete', u'complete'), (u' L ', u' I '), (u'a/ion', u'ation'), (u'\xc2s', u"'s"), (u"'tjust", u"'t just")]),
'PartialWordsAlways': {'data': OrderedDict([(u'\xa4', u'o'), (u'lVI', u'M'), (u'IVl', u'M'), (u'lVl', u'M'), (u'I\\/I', u'M'), (u'l\\/I', u'M'), (u'I\\/l', u'M'), (u'l\\/l', u'M'), (u'IVIa', u'Ma'), (u'IVIe', u'Me'), (u'IVIi', u'Mi'), (u'IVIo', u'Mo'), (u'IVIu', u'Mu'), (u'IVIy', u'My'), (u' l ', u' I '), (u'l/an', u'lian'), (u'\xb0x\xb0', u'%'), (u'\xc3\xc2s', u"'s"), (u'at/on', u'ation'), (u'lljust', u'll just'), (u"'sjust", u"'s just"), (u'compiete', u'complete'), (u' L ', u' I '), (u'a/ion', u'ation'), (u'\xc2s', u"'s"), (u"'tjust", u"'t just")]),
'pattern': None},
'WholeLines': {'data': OrderedDict([(u'H ey.', u'Hey.'), (u'He)\u2019-', u'Hey.'), (u'N0.', u'No.'), (u'-N0.', u'-No.'), (u'Noll', u'No!!'), (u'(G ROANS)', u'(GROANS)'), (u'[G ROANS]', u'[GROANS]'), (u'(M EOWS)', u'(MEOWS)'), (u'[M EOWS]', u'[MEOWS]'), (u'Uaughs]', u'[laughs]'), (u'[chitte rs]', u'[chitters]'), (u'Hil\u2018 it!', u'Hit it!'), (u'<i>Hil\u2018 it!</i>', u'<i>Hit it!</i>'), (u'ISIGHS]', u'[SIGHS]')]),
'pattern': None},
@@ -36,7 +36,6 @@ SZ_FIX_DATA = {
u" l ": u" I ",
u"'sjust": u"'s just",
u"'tjust": u"'t just",
u"\";": u"'s",
},
"WholeWords": {
u"I'11": u"I'll",
@@ -6,7 +6,7 @@ import pysubs2
import logging
import time
from mods import EMPTY_TAG_PROCESSOR, EmptyEntryError
from mods import EMPTY_TAG_PROCESSOR, EmptyEntryError, FullContentRep
from registry import registry
from subzero.language import Language
@@ -257,7 +257,16 @@ class SubtitleModifications(object):
mod.modify(None, debug=self.debug, parent=self, **args)
def apply_line_mods(self, new_entries, mods):
for index, entry in enumerate(self.f, 1):
index = 1
entries = self.f[:]
entry_count = len(entries)
while 1:
if index > entry_count - 1:
break
entry = entries[index]
applied_mods = []
lines = []
@@ -265,116 +274,110 @@ class SubtitleModifications(object):
start_tags = []
end_tags = []
t = entry.text.strip()
if not t:
text = entry.text.replace(ur"\N", "\n").strip()
if not text:
if self.debug:
logger.debug(u"Skipping empty line: %s", index)
index += 1
continue
skip_entry = False
for line in t.split(ur"\N"):
# don't bother the mods with surrounding tags
old_line = line
line = line.strip()
skip_line = False
line_count += 1
try:
for line in text.split("\n"):
# don't bother the mods with surrounding tags
old_line = line
line = line.strip()
skip_line = False
line_count += 1
if not line:
continue
if not line:
continue
# clean {\X0} tags before processing
# fixme: handle nested tags?
start_tag = u""
end_tag = u""
if line.startswith(self.font_style_tag_start):
start_tag = line[:5]
line = line[5:]
if line[-5:-3] == self.font_style_tag_start:
end_tag = line[-5:]
line = line[:-5]
# clean {\X0} tags before processing
# fixme: handle nested tags?
start_tag = u""
end_tag = u""
if line.startswith(self.font_style_tag_start):
start_tag = line[:5]
line = line[5:]
if line[-5:-3] == self.font_style_tag_start:
end_tag = line[-5:]
line = line[:-5]
last_procs_mods = []
last_procs_mods = []
# fixme: this double loop is ugly
for order, identifier, args in mods:
mod = self.initialized_mods[identifier]
# fixme: this double loop is ugly
for order, identifier, args in mods:
mod = self.initialized_mods[identifier]
try:
line = mod.modify(line.strip(), entry=entry.text, debug=self.debug, parent=self, index=index,
line = mod.modify(line.strip(), entry=text, debug=self.debug, parent=self, index=index,
**args)
except EmptyEntryError:
if self.debug:
logger.debug(u"%d: %s: %r -> ''", index, identifier, entry.text)
skip_entry = True
break
if not line:
if self.debug:
logger.debug(u"%d: %s: %r -> ''", index, identifier, old_line)
skip_line = True
break
if not line:
if self.debug:
logger.debug(u"%d: %s: %r -> ''", index, identifier, old_line)
skip_line = True
break
applied_mods.append(identifier)
if mod.last_processors:
last_procs_mods.append([identifier, args])
applied_mods.append(identifier)
if mod.last_processors:
last_procs_mods.append([identifier, args])
if skip_entry:
lines = []
break
if skip_line:
continue
if skip_line:
continue
for identifier, args in last_procs_mods:
mod = self.initialized_mods[identifier]
for identifier, args in last_procs_mods:
mod = self.initialized_mods[identifier]
try:
line = mod.modify(line.strip(), entry=entry.text, debug=self.debug, parent=self, index=index,
line = mod.modify(line.strip(), entry=text, debug=self.debug, parent=self, index=index,
procs=["last_process"], **args)
except EmptyEntryError:
if not line:
if self.debug:
logger.debug(u"%d: %s: %r -> ''", index, identifier, old_line)
skip_line = True
break
if skip_line:
continue
if start_tag:
start_tags.append(start_tag)
if end_tag:
end_tags.append(end_tag)
# append new line and clean possibly newly added empty tags
cleaned_line = EMPTY_TAG_PROCESSOR.process(start_tag + line + end_tag, debug=self.debug).strip()
if cleaned_line:
# we may have a single closing tag, if so, try appending it to the previous line
if len(cleaned_line) == 5 and cleaned_line.startswith("{\\") and cleaned_line.endswith("0}"):
if lines:
prev_line = lines.pop()
lines.append(prev_line + cleaned_line)
continue
lines.append(cleaned_line)
else:
if self.debug:
logger.debug(u"%d: %s: %r -> ''", index, identifier, entry.text)
skip_entry = True
break
logger.debug(u"%d: Ditching now empty line (%r)", index, line)
if not line:
if self.debug:
logger.debug(u"%d: %s: %r -> ''", index, identifier, old_line)
skip_line = True
break
if skip_entry:
lines = []
break
if skip_line:
if not lines:
# don't bother logging when the entry only had one line
if self.debug and line_count > 1:
logger.debug(u"%d: %r -> ''", index, text)
index += 1
continue
except EmptyEntryError, e:
if self.debug:
logger.debug(u"%d: %s: %r -> ''", index, e.mod.identifier, e.entry)
index += 1
continue
if start_tag:
start_tags.append(start_tag)
if end_tag:
end_tags.append(end_tag)
# append new line and clean possibly newly added empty tags
cleaned_line = EMPTY_TAG_PROCESSOR.process(start_tag + line + end_tag, debug=self.debug).strip()
if cleaned_line:
# we may have a single closing tag, if so, try appending it to the previous line
if len(cleaned_line) == 5 and cleaned_line.startswith("{\\") and cleaned_line.endswith("0}"):
if lines:
prev_line = lines.pop()
lines.append(prev_line + cleaned_line)
continue
lines.append(cleaned_line)
else:
if self.debug:
logger.debug(u"%d: Ditching now empty line (%r)", index, line)
if not lines:
# don't bother logging when the entry only had one line
if self.debug and line_count > 1:
logger.debug(u"%d: %r -> ''", index, entry.text)
except FullContentRep, e:
if self.debug:
logger.debug(u"%d: %s: %r -> %r", index, e.mod.identifier, text, e.new_content)
new_entries.append(e.new_content.replace("\n", ur"\N"))
index += 1
continue
new_text = ur"\N".join(lines)
@@ -403,6 +406,8 @@ class SubtitleModifications(object):
entry.text = new_text
new_entries.append(entry)
index += 1
SubMod = SubtitleModifications
@@ -47,7 +47,7 @@ class SubtitleModification(object):
continue
old_content = new_content
new_content = processor.process(new_content, debug=debug, **kwargs)
new_content = processor.process(new_content, debug=debug, mod=self, **kwargs)
if not new_content:
if debug:
logger.debug("Processor returned empty line: %s", processor.name)
@@ -107,9 +107,22 @@ empty_line_post_processors = [
]
class EmptyEntryError(Exception):
class ModEvent(Exception):
def __init__(self, *args, **kwargs):
self.mod = kwargs.pop("mod", None)
self.entry = kwargs.pop("entry", None)
super(ModEvent, self).__init__(*args, **kwargs)
class EmptyEntryError(ModEvent):
pass
class EmptyLineError(Exception):
class EmptyLineError(ModEvent):
pass
class FullContentRep(ModEvent):
def __init__(self, *args, **kwargs):
self.new_content = kwargs.pop("new_content", None)
super(FullContentRep, self).__init__(*args, **kwargs)
@@ -1,7 +1,8 @@
# coding=utf-8
import re
from subzero.modification.mods import SubtitleTextModification, empty_line_post_processors, EmptyEntryError, TAG
from subzero.modification.mods import SubtitleTextModification, empty_line_post_processors, EmptyEntryError, TAG, \
FullContentRep
from subzero.modification.processors.re_processor import NReProcessor
from subzero.modification import registry
@@ -10,9 +11,11 @@ class FullBracketEntryProcessor(NReProcessor):
def process(self, content, debug=False, **kwargs):
entry = kwargs.get("entry")
if entry:
rep_content = super(FullBracketEntryProcessor, self).process(entry, debug=debug, **kwargs)
if not rep_content.strip():
raise EmptyEntryError()
rep_content = super(FullBracketEntryProcessor, self).process(entry, debug=debug, **kwargs).strip()
if not rep_content:
raise EmptyEntryError(mod=self.mod, entry=entry)
if content != rep_content:
raise FullContentRep(new_content=rep_content, mod=self.mod, entry=entry)
return content
@@ -49,8 +52,8 @@ class HearingImpaired(SubtitleTextModification):
NReProcessor(re.compile(ur'(?sux)-?%(t)s[([][^([)\]]+?(?=[A-zÀ-ž"\'.]{3,})[^([)\]]+[)\]][\s:]*%(t)s' %
{"t": TAG}), "", name="HI_brackets"),
#NReProcessor(re.compile(ur'(?sux)-?%(t)s[([]%(t)s(?=[A-zÀ-ž"\'.]{3,})[^([)\]]+%(t)s$' % {"t": TAG}),
# "", name="HI_bracket_open_start"),
FullBracketEntryProcessor(re.compile(ur'(?sux)-?%(t)s[([]%(t)s(?=[A-zÀ-ž"\'.]{3,})[^([)\]]+%(t)s$' % {"t": TAG}),
"", name="HI_bracket_open_start"),
#NReProcessor(re.compile(ur'(?sux)-?%(t)s(?=[A-zÀ-ž"\'.]{3,})[^([)\]]+[)\]][\s:]*%(t)s' % {"t": TAG}), "",
# name="HI_bracket_open_end"),
@@ -7,12 +7,14 @@ class Processor(object):
"""
name = None
parent = None
mod = None
supported = None
enabled = True
def __init__(self, name=None, parent=None, supported=None):
def __init__(self, name=None, parent=None, mod=None, supported=None):
self.name = name
self.parent = parent
self.mod = mod
self.supported = supported if supported else lambda parent: True
@property
@@ -20,6 +22,8 @@ class Processor(object):
return self.name
def process(self, content, debug=False, **kwargs):
if not self.mod:
self.mod = kwargs.get("mod", None)
return content
def __repr__(self):
@@ -14,12 +14,13 @@ class ReProcessor(Processor):
pattern = None
replace_with = None
def __init__(self, pattern, replace_with, name=None, supported=None):
super(ReProcessor, self).__init__(name=name, supported=supported)
def __init__(self, pattern, replace_with, name=None, supported=None, **kwargs):
super(ReProcessor, self).__init__(name=name, supported=supported, **kwargs)
self.pattern = pattern
self.replace_with = replace_with
def process(self, content, debug=False, **kwargs):
super(ReProcessor, self).process(content, debug=debug, **kwargs)
return self.pattern.sub(self.replace_with, content)
+2 -2
View File
@@ -52,10 +52,10 @@ def set_existing_languages(video, video_info, external_subtitles=False, embedded
video.subtitle_languages.add(language)
def parse_video(fn, hints, skip_hashing=False, dry_run=False, providers=None, hash_from=None):
def parse_video(fn, hints, skip_hashing=False, dry_run=False, providers=None):
logger.debug("Parsing video: %s, hints: %s", os.path.basename(fn), hints)
return scan_video(fn, hints=hints, dont_use_actual_file=dry_run, providers=providers,
skip_hashing=skip_hashing, hash_from=hash_from)
skip_hashing=skip_hashing)
def refine_video(video, no_refining=False, refiner_settings=None):
+12 -12
View File
@@ -91,28 +91,28 @@ the.vbm, mmgoodnow, Vertig0ne, thliu78, tattoomees, ostman, count_confucius, ehe
## Changelog
2.6.5.3124
2.6.5.3074
subscene, addic7ed and titlovi
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
Changelog
- core: http: fallback to default DNS when normal resolving fails; fixes #657
- core: extract embedded/menu: fix detection of unknown streams; don't use unknown streams if a known language was previously found
- core: language: use replacement map from bazarr
- providers: titlovi: fix matching
- providers: subscene: fix unknown language code error when "empty" result is returned
- providers: subscene: add support for pt-BR (based on Diaoul/subliminal@b22cf08)
- providers: subscene: explicitly set account filters for languages
- providers: subscene: limit alternative searches to 3; set throttle to 8
- providers: subscene: move login/cookies to initialization sequence
- submod: generic: en: fix ";='s
- core: cf: bypass cf 95% of the time without captchas
- core: fix breaking line endings of certain languages (chinese, UTF-16); fixes #646
- core: update pysubs2 to 0.2.3
2.6.5.3062
Changelog
- core: cf: optimize
- core: http: don't query DNS with IPs. thanks @fgump (fixes sonarr/radarr)
[older changes](CHANGELOG.md)
Subtitles provided by [OpenSubtitles.org](http://www.opensubtitles.org/), [Podnapisi.NET](https://www.podnapisi.net/), [TVSubtitles.net](http://www.tvsubtitles.net/), [Addic7ed.com](http://www.addic7ed.com/), [Legendas TV](http://legendas.tv/), [Napi Projekt](http://www.napiprojekt.pl/), [Shooter](http://shooter.cn/), [Titlovi](http://titlovi.com), [aRGENTeaM](http://argenteam.net), [SubScene](https://subscene.com/), [Hosszupuska](http://hosszupuskasub.com/), [Napisy24](https://napisy24.pl/)
Subtitles provided by [OpenSubtitles.org](http://www.opensubtitles.org/), [Podnapisi.NET](https://www.podnapisi.net/), [TVSubtitles.net](http://www.tvsubtitles.net/), [Addic7ed.com](http://www.addic7ed.com/), [Legendas TV](http://legendas.tv/), [Napi Projekt](http://www.napiprojekt.pl/), [Shooter](http://shooter.cn/), [Titlovi](http://titlovi.com), [aRGENTeaM](http://argenteam.net), [SubScene](https://subscene.com/), [Hosszupuska](http://hosszupuskasub.com/)
[3rd party licenses](https://github.com/pannal/Sub-Zero.bundle/tree/master/Licenses)