Compare commits

...

15 Commits

Author SHA1 Message Date
pannal 3a09d9ffee Update README.md 2024-01-27 02:28:07 +01:00
pannal 62439ef49f fix advanced_settings.json.template 2024-01-27 01:45:43 +01:00
pannal 4458795293 Update README.md 2024-01-27 00:11:07 +01:00
pannal e648e945c9 Update README.md 2024-01-27 00:09:06 +01:00
pannal 3d95c6e420 Update README.md 2024-01-27 00:08:25 +01:00
pannal 20c3cd2340 2.6.5.3280 2024-01-27 00:06:17 +01:00
pannal 483e0cf96e extremely cheap and barely tested replacement of OpenSubtitles.org with OpenSubtitles.com (needs your own API key) 2024-01-27 00:00:00 +01:00
pannal 4ced7d8c8f remove old TLD file due to licensing issues 2023-07-09 15:08:04 +02:00
pannal f888cadb8f release 2.6.5.3277 2023-07-09 04:49:32 +02:00
pannal ccf264cffb core: fix enabled library/agents detection (Plex removed certain features) 2023-07-09 04:42:24 +02:00
panni 19a66dcf1c core: fix adding guessit-supplied titles as alternative titles (were overridden by plex before) 2023-07-09 04:42:24 +02:00
panni 67b20b357d back to dev 2023-07-09 04:42:24 +02:00
pannal d33bc1b148 Merge pull request #788 from sugarman402/patch-1
Change supersubtitles server url
2022-11-28 04:23:11 +01:00
Tamás Németh 8de63e92e5 Change supersupbtitles server url
Supersubtitles' url changed from feliratok.info to feliratok.eu
2022-06-05 18:22:01 +02:00
pannal 2788b0e0b2 Update README.md 2021-05-07 02:36:16 +02:00
13 changed files with 704 additions and 4452 deletions
+13
View File
@@ -1,3 +1,16 @@
2.6.5.3280
temporarily enable OpenSubtitles.com instead of OpenSubtitles.org.
You need to have an account there and an API consumer configured. Enter your API key in settings.
This is barely tested but should work for basic usage.
THIS PLUGIN IS DEPRECATED, PLEASE USE BAZARR!
Changelog
- cheaply backport opensubtitlescom from bazarr
2.6.5.3268
subscene, addic7ed
- 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
+29 -11
View File
@@ -86,6 +86,10 @@ PROVIDER_THROTTLE_MAP = {
DownloadLimitExceeded: (datetime.timedelta(hours=6), "6 hours"),
APIThrottled: (datetime.timedelta(seconds=15), "15 seconds"),
},
"opensubtitlescom": {
TooManyRequests: (datetime.timedelta(minutes=1), "1 minute"),
DownloadLimitExceeded: (datetime.timedelta(hours=24), "24 hours"),
},
"addic7ed": {
DownloadLimitExceeded: (datetime.timedelta(hours=3), "3 hours"),
TooManyRequests: (datetime.timedelta(minutes=5), "5 minutes"),
@@ -671,12 +675,23 @@ class Config(object):
enabled_for_primary_agents = {"movie": [], "show": []}
enabled_sections = {}
legacy_agents = {
"com.plexapp.agents.thetvdb": [SHOW],
"com.plexapp.agents.thetvdbdvdorder": [SHOW],
"com.plexapp.agents.hama": [SHOW, MOVIE],
"com.plexapp.agents.themoviedb": [SHOW, MOVIE],
"com.plexapp.agents.imdb": [SHOW, MOVIE],
}
# find which agents we're enabled for
for agent in Plex.agents():
if not agent.primary:
#if not agent.primary:
# continue
if agent.identifier not in legacy_agents:
continue
media_types = [t.media_type for t in list(agent.media_types)]
#media_types = [t.media_type for t in list(agent.media_types)]
media_types = legacy_agents[agent.identifier] + []
# the new movie agent doesn't populate its media types, workaround
if not media_types and agent.identifier == "tv.plex.agents.movie":
@@ -816,7 +831,7 @@ class Config(object):
@property
def providers_by_prefs(self):
return {'opensubtitles': cast_bool(Prefs['provider.opensubtitles.enabled']),
return {'opensubtitlescom': 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']),
@@ -914,15 +929,18 @@ class Config(object):
'password': Prefs['provider.addic7ed.password'],
'is_vip': cast_bool(Prefs['provider.addic7ed.is_vip']),
},
'opensubtitles': {'username': Prefs['provider.opensubtitles.username'],
'opensubtitlescom': {'username': Prefs['provider.opensubtitles.username'],
'password': Prefs['provider.opensubtitles.password'],
'use_tag_search': self.exact_filenames,
'only_foreign': self.forced_only,
'also_foreign': self.forced_also,
'is_vip': cast_bool(Prefs['provider.opensubtitles.is_vip']),
'use_ssl': os_use_https,
'timeout': self.advanced.providers.opensubtitles.timeout or 15,
'skip_wrong_fps': os_skip_wrong_fps,
#'use_tag_search': self.exact_filenames,
#'only_foreign': self.forced_only,
#'also_foreign': self.forced_also,
#'is_vip': cast_bool(Prefs['provider.opensubtitles.is_vip']),
#'use_ssl': os_use_https,
#'timeout': self.advanced.providers.opensubtitles.timeout or 15,
#'skip_wrong_fps': os_skip_wrong_fps,
'use_hash': cast_bool(Prefs['provider.opensubtitles.use_hash']),
'include_ai_translated': True,
'api_key': Prefs['provider.opensubtitles.api_key'],
},
'podnapisi': {
'only_foreign': self.forced_only,
+2 -2
View File
@@ -128,8 +128,8 @@ class SubtitleListingMixin(object):
config.init_subliminal_patches()
provider_settings = config.provider_settings
if not skip_wrong_fps:
provider_settings["opensubtitles"]["skip_wrong_fps"] = False
#if not skip_wrong_fps:
# provider_settings["opensubtitlescom"]["skip_wrong_fps"] = False
if item_type == "episode":
min_score = 240
+10 -4
View File
@@ -311,7 +311,7 @@
},
{
"id": "provider.opensubtitles.enabled",
"label": "Provider: Enable OpenSubtitles",
"label": "Provider: Enable OpenSubtitles.com",
"type": "bool",
"default": "true"
},
@@ -330,10 +330,16 @@
"secure": "true"
},
{
"id": "provider.opensubtitles.is_vip",
"label": "OpenSubtitles VIP? (ad-free subs, 1000 subs/day, no-cache VIP server: http://v.ht/osvip)",
"id": "provider.opensubtitles.use_hash",
"label": "OpenSubtitles hash?",
"type": "bool",
"default": "false"
"default": "true"
},
{
"id": "provider.opensubtitles.api_key",
"label": "OpenSubtitles APIKey",
"type": "text",
"default": ""
},
{
"id": "provider.podnapisi.enabled",
+2 -2
View File
@@ -13,7 +13,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.6.5.3268</string>
<string>2.6.5.3280</string>
<key>PlexFrameworkVersion</key>
<string>2</string>
<key>PlexPluginClass</key>
@@ -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.3268
Version 2.6.5.3280
Originally based on @bramwalet's awesome &lt;a href=&quot;https://github.com/bramwalet/Subliminal.bundle&quot;&gt;Subliminal.bundle&lt;/a&gt;
@@ -529,7 +529,7 @@ def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, ski
video.hints = hints
# get possibly alternative title from the filename itself
alt_guess = guessit(filename, options=hints)
alt_guess = guessit(filename, options={k: v for k, v in hints.items() if k not in ('expected_title', 'title')})
if "title" in alt_guess and alt_guess["title"] != guessed_result["title"]:
if video_type == "episode":
video.alternative_series.append(alt_guess["title"])
@@ -554,6 +554,9 @@ def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, ski
if "opensubtitles" in providers:
video.hashes['opensubtitles'] = osub_hash = osub_hash or hash_opensubtitles(hash_path)
if "opensubtitlescom" in providers:
video.hashes['opensubtitlescom'] = osub_hash = osub_hash or hash_opensubtitles(hash_path)
if "shooter" in providers:
video.hashes['shooter'] = hash_shooter(hash_path)
@@ -0,0 +1,579 @@
# -*- coding: utf-8 -*-
import logging
import os
import time
import datetime
import json
import types
from requests import Session, ConnectionError, Timeout, ReadTimeout, RequestException
from simplejson import JSONDecodeError
from subzero.language import Language
from babelfish import language_converters
from subliminal import Episode, Movie
from subliminal.score import get_equivalent_release_groups
from subliminal.utils import sanitize_release_group, sanitize
from subliminal_patch.exceptions import TooManyRequests, APIThrottled
from subliminal.exceptions import DownloadLimitExceeded, AuthenticationError, ConfigurationError, ServiceUnavailable, \
ProviderError
from .mixins import ProviderRetryMixin
from subliminal_patch.subtitle import Subtitle
from subliminal.subtitle import fix_line_ending, SUBTITLE_EXTENSIONS
from subliminal_patch.providers import Provider
from subliminal_patch.subtitle import guess_matches
from subliminal_patch.utils import fix_inconsistent_naming
from subliminal.cache import region
from dogpile.cache.api import NO_VALUE
from guessit import guessit
logger = logging.getLogger(__name__)
SHOW_EXPIRATION_TIME = datetime.timedelta(weeks=1).total_seconds()
TOKEN_EXPIRATION_TIME = datetime.timedelta(hours=12).total_seconds()
retry_amount = 3
def fix_tv_naming(title):
"""Fix TV show titles with inconsistent naming using dictionary, but do not sanitize them.
:param str title: original title.
:return: new title.
:rtype: str
"""
return fix_inconsistent_naming(title, {"Superman & Lois": "Superman and Lois",
}, True)
def fix_movie_naming(title):
return fix_inconsistent_naming(title, {
}, True)
custom_languages = {
'pt': 'pt-PT',
'zh': 'zh-CN',
}
def to_opensubtitlescom(lang):
if lang in custom_languages.keys():
return custom_languages[lang]
else:
return lang
def from_opensubtitlescom(lang):
from_custom_languages = {v: k for k, v in custom_languages.items()}
if lang in from_custom_languages.keys():
return from_custom_languages[lang]
else:
return lang
class OpenSubtitlesComSubtitle(Subtitle):
provider_name = 'opensubtitlescom'
hash_verifiable = True
hearing_impaired_verifiable = True
def __init__(self, language, forced, hearing_impaired, page_link, file_id, releases, uploader, title, year,
hash_matched, file_hash=None, season=None, episode=None, imdb_match=False):
super(OpenSubtitlesComSubtitle, self).__init__(language, hearing_impaired, page_link)
language = Language.rebuild(language, hi=hearing_impaired, forced=forced)
self.title = title
self.year = year
self.season = season
self.episode = episode
self.releases = releases
self.release_info = releases
self.language = language
self.hearing_impaired = hearing_impaired
self.forced = forced
self.file_id = file_id
self.page_link = page_link
self.download_link = None
self.uploader = uploader
self.matches = None
self.hash = file_hash
self.encoding = 'utf-8'
self.hash_matched = hash_matched
self.imdb_match = imdb_match
@property
def id(self):
return self.file_id
def get_matches(self, video):
matches = set()
type_ = "movie" if isinstance(video, Movie) else "episode"
# handle movies and series separately
if type_ == "episode":
# series
matches.add('series')
# season
if video.season == self.season:
matches.add('season')
# episode
if video.episode == self.episode:
matches.add('episode')
# imdb
if self.imdb_match:
matches.add('series_imdb_id')
else:
# title
matches.add('title')
# imdb
if self.imdb_match:
matches.add('imdb_id')
# rest is same for both groups
# year
if video.year == self.year:
matches.add('year')
# release_group
if (video.release_group and self.releases and
any(r in sanitize_release_group(self.releases)
for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))):
matches.add('release_group')
if self.hash_matched:
matches.add('hash')
# other properties
matches |= guess_matches(video, guessit(self.releases, {"type": type_}))
self.matches = matches
return matches
class OpenSubtitlesComProvider(ProviderRetryMixin, Provider):
"""OpenSubtitlesCom Provider"""
server_url = 'https://api.opensubtitles.com/api/v1/'
languages = {Language.fromopensubtitles(lang) for lang in language_converters['szopensubtitles'].codes}
languages.update(set(Language.rebuild(lang, forced=True) for lang in languages))
languages.update(set(Language.rebuild(l, hi=True) for l in languages))
video_types = (Episode, Movie)
def __init__(self, username=None, password=None, use_hash=True, include_ai_translated=False, api_key=None):
if not all((username, password)):
raise ConfigurationError('Username and password must be specified')
if not api_key:
raise ConfigurationError('Api_key must be specified')
if not all((username, password)):
raise ConfigurationError('Username and password must be specified')
self.session = Session()
self.session.headers = {'User-Agent': os.environ.get("SZ_USER_AGENT", "Sub-Zero/2"),
'Api-Key': api_key,
'Content-Type': 'application/json'}
self.token = None
self.username = username
self.password = password
self.video = None
self.use_hash = use_hash
self.include_ai_translated = include_ai_translated
self._started = None
def initialize(self):
self._started = time.time()
if region.get("oscom_token", expiration_time=TOKEN_EXPIRATION_TIME) is NO_VALUE:
logger.debug("No cached token, we'll try to login again.")
self.login()
else:
self.token = region.get("oscom_token", expiration_time=TOKEN_EXPIRATION_TIME)
def terminate(self):
self.session.close()
def ping(self):
return self._started and (time.time() - self._started) < TOKEN_EXPIRATION_TIME
def login(self, is_retry=False):
r = self.checked(
lambda: self.session.post(self.server_url + 'login',
json={"username": self.username, "password": self.password},
allow_redirects=False,
timeout=30),
is_retry=is_retry)
try:
self.token = r.json()['token']
except (ValueError, JSONDecodeError):
log_request_response(r)
raise ProviderError("Cannot get token from provider login response")
else:
log_request_response(r, non_standard=False)
region.set("oscom_token", self.token)
@staticmethod
def sanitize_external_ids(external_id):
if isinstance(external_id, types.StringTypes):
external_id = external_id.lower().lstrip('tt').lstrip('0')
sanitized_id = external_id[:-1].lstrip('0') + external_id[-1]
return int(sanitized_id)
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)
def search_titles(self, title):
title_id = None
parameters = {'query': title.lower()}
logging.debug('Searching using this title: %s' % title)
results = self.retry(
lambda: self.checked(
lambda: self.session.get(self.server_url + 'features', params=parameters, timeout=30),
validate_json=True,
json_key_name='data'
),
amount=retry_amount
)
# deserialize results
results_dict = results.json()['data']
# loop over results
for result in results_dict:
if 'title' in result['attributes']:
if isinstance(self.video, Episode):
if fix_tv_naming(title).lower() == result['attributes']['title'].lower() and \
(not self.video.year or self.video.year == int(result['attributes']['year'])):
title_id = result['id']
break
else:
if fix_movie_naming(title).lower() == result['attributes']['title'].lower() and \
(not self.video.year or self.video.year == int(result['attributes']['year'])):
title_id = result['id']
break
else:
continue
if title_id:
logging.debug('Found this title ID: %s' % title_id)
return self.sanitize_external_ids(title_id)
if not title_id:
logger.debug('No match found for %s' % title)
def query(self, languages, video):
self.video = video
if self.use_hash:
file_hash = self.video.hashes.get('opensubtitlescom')
logging.debug('Searching using this hash: %s' % hash)
else:
file_hash = None
if isinstance(self.video, Episode):
title = self.video.series
else:
title = self.video.title
imdb_id = None
if isinstance(self.video, Episode) and self.video.series_imdb_id:
imdb_id = self.sanitize_external_ids(self.video.series_imdb_id)
elif isinstance(self.video, Movie) and self.video.imdb_id:
imdb_id = self.sanitize_external_ids(self.video.imdb_id)
title_id = None
if not imdb_id:
title_id = self.search_titles(title)
if not title_id:
return []
# be sure to remove duplicates using list(set())
langs_list = sorted(list(set([to_opensubtitlescom(lang.basename).lower() for lang in languages])))
langs = ','.join(langs_list)
logging.debug('Searching for those languages: %s' % langs)
# query the server
if isinstance(self.video, Episode):
res = self.retry(
lambda: self.checked(
lambda: self.session.get(self.server_url + 'subtitles',
params=(('ai_translated', 'exclude' if not self.include_ai_translated
else 'include'),
('episode_number', self.video.episode),
('imdb_id', imdb_id if not title_id else None),
('languages', langs),
('moviehash', file_hash),
('parent_feature_id', title_id if title_id else None),
('season_number', self.video.season)),
timeout=30),
validate_json=True,
json_key_name='data'
),
amount=retry_amount
)
else:
res = self.retry(
lambda: self.checked(
lambda: self.session.get(self.server_url + 'subtitles',
params=(('ai_translated', 'exclude' if not self.include_ai_translated
else 'include'),
('id', title_id if title_id else None),
('imdb_id', imdb_id if not title_id else None),
('languages', langs),
('moviehash', file_hash)),
timeout=30),
validate_json=True,
json_key_name='data'
),
amount=retry_amount
)
subtitles = []
result = res.json()
# filter out forced subtitles or not depending on the required languages
if all([lang.forced for lang in languages]): # only forced
result['data'] = [x for x in result['data'] if x['attributes']['foreign_parts_only']]
elif any([lang.forced for lang in languages]): # also forced
pass
else: # not forced
result['data'] = [x for x in result['data'] if not x['attributes']['foreign_parts_only']]
logging.debug("Query returned %s subtitles" % len(result['data']))
if len(result['data']):
for item in result['data']:
# ignore AI translated subtitles
if 'ai_translated' in item['attributes'] and item['attributes']['ai_translated']:
logging.debug("Skipping AI translated subtitles")
continue
# ignore machine translated subtitles
if 'machine_translated' in item['attributes'] and item['attributes']['machine_translated']:
logging.debug("Skipping machine translated subtitles")
continue
if 'season_number' in item['attributes']['feature_details']:
season_number = item['attributes']['feature_details']['season_number']
else:
season_number = None
if 'episode_number' in item['attributes']['feature_details']:
episode_number = item['attributes']['feature_details']['episode_number']
else:
episode_number = None
if 'moviehash_match' in item['attributes']:
moviehash_match = item['attributes']['moviehash_match']
else:
moviehash_match = False
try:
year = int(item['attributes']['feature_details']['year'])
except TypeError:
year = item['attributes']['feature_details']['year']
if len(item['attributes']['files']):
subtitle = OpenSubtitlesComSubtitle(
language=Language.fromietf(from_opensubtitlescom(item['attributes']['language'])),
forced=item['attributes']['foreign_parts_only'],
hearing_impaired=item['attributes']['hearing_impaired'],
page_link=item['attributes']['url'],
file_id=item['attributes']['files'][0]['file_id'],
releases=item['attributes']['release'],
uploader=item['attributes']['uploader']['name'],
title=item['attributes']['feature_details']['movie_name'],
year=year,
season=season_number,
episode=episode_number,
hash_matched=moviehash_match,
imdb_match=True if imdb_id else False
)
subtitle.get_matches(self.video)
subtitles.append(subtitle)
return subtitles
def list_subtitles(self, video, languages):
return self.query(languages, video)
def download_subtitle(self, subtitle):
logger.info('Downloading subtitle %r', subtitle)
headers = {'Accept': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + self.token}
res = self.retry(
lambda: self.checked(
lambda: self.session.post(self.server_url + 'download',
json={'file_id': subtitle.file_id, 'sub_format': 'srt'},
headers=headers,
timeout=30),
validate_json=True,
json_key_name='link'
),
amount=retry_amount
)
download_data = res.json()
subtitle.download_link = download_data['link']
r = self.retry(
lambda: self.checked(
lambda: self.session.get(subtitle.download_link, timeout=30),
validate_content=True
),
amount=retry_amount
)
if not r:
logger.debug('Could not download subtitle from %s' % subtitle.download_link)
subtitle.content = None
return
else:
subtitle_content = r.content
subtitle.content = fix_line_ending(subtitle_content)
@staticmethod
def reset_token():
logging.debug('Authentication failed: clearing cache and attempting to login.')
region.delete("oscom_token")
return
def checked(self, fn, raise_api_limit=False, validate_json=False, json_key_name=None, validate_content=False,
is_retry=False):
"""Run :fn: and check the response status before returning it.
:param fn: the function to make an API call to OpenSubtitles.com.
:param raise_api_limit: if True we wait a little bit longer before running the call again.
:param validate_json: test if response is valid json.
:param json_key_name: test if returned json contain a specific key.
:param validate_content: test if response have a content (used with download).
:param is_retry: prevent additional retries with login endpoint.
:return: the response.
"""
response = None
try:
try:
response = fn()
except APIThrottled:
if not raise_api_limit:
logger.info("API request limit hit, waiting and trying again once.")
time.sleep(15)
return self.checked(fn, raise_api_limit=True)
raise
except (ConnectionError, Timeout, ReadTimeout):
raise ServiceUnavailable('Unknown Error, empty response: {}: {}'.format(response.status_code, response))
except Exception:
logging.exception('Unhandled exception raised.')
raise ProviderError('Unhandled exception raised. Check log.')
else:
status_code = response.status_code
except Exception:
status_code = None
else:
if status_code == 400:
try:
json_response = response.json()
message = json_response['message']
except JSONDecodeError:
raise ProviderError('Invalid JSON returned by provider')
else:
log_request_response(response)
raise ConfigurationError(message)
elif status_code == 401:
log_request_response(response)
self.reset_token()
if is_retry:
raise AuthenticationError('Login failed')
else:
time.sleep(1)
self.login(is_retry=True)
self.checked(fn, raise_api_limit=raise_api_limit, validate_json=validate_json,
json_key_name=json_key_name, validate_content=validate_content, is_retry=True)
elif status_code == 403:
log_request_response(response)
raise ProviderError("Bazarr API key seems to be in problem")
elif status_code == 406:
try:
json_response = response.json()
download_count = json_response['requests']
remaining_download = json_response['remaining']
quota_reset_time = json_response['reset_time']
except JSONDecodeError:
raise ProviderError('Invalid JSON returned by provider')
else:
log_request_response(response)
raise DownloadLimitExceeded("Daily download limit reached. {} subtitles have been "
"downloaded and {} remaining subtitles can be "
"downloaded. Quota will be reset in {}.".format(download_count, remaining_download, quota_reset_time))
elif status_code == 410:
log_request_response(response)
raise ProviderError("Download as expired")
elif status_code == 429:
log_request_response(response)
raise TooManyRequests()
elif status_code == 500:
logging.debug("Server side exception raised while downloading from opensubtitles.com website. They "
"should mitigate this soon.")
return None
elif status_code == 502:
# this one should deal with Bad Gateway issue on their side.
raise APIThrottled()
elif 500 <= status_code <= 599:
raise ProviderError(response.reason)
if status_code != 200:
log_request_response(response)
raise ProviderError('Bad status code: %s' % response.status_code)
if validate_json:
try:
json_test = response.json()
except JSONDecodeError:
raise ProviderError('Invalid JSON returned by provider')
else:
if json_key_name not in json_test:
raise ProviderError('Invalid JSON returned by provider: no %s key in returned json.' % json_key_name)
if validate_content:
if not hasattr(response, 'content'):
logging.error('Download link returned no content attribute.')
return False
elif not response.content:
logging.error('This download link returned empty content: %s' % response.url)
return False
return response
def log_request_response(response, non_standard=True):
redacted_request_headers = response.request.headers
if 'Authorization' in redacted_request_headers and isinstance(redacted_request_headers['Authorization'], str):
redacted_request_headers['Authorization'] = redacted_request_headers['Authorization'][:-8]+8*'x'
redacted_request_body = json.loads(response.request.body)
if 'password' in redacted_request_body:
redacted_request_body['password'] = 'redacted'
redacted_response_body = json.loads(response.text)
if 'token' in redacted_response_body and isinstance(redacted_response_body['token'], str):
redacted_response_body['token'] = redacted_response_body['token'][:-8] + 8 * 'x'
if non_standard:
logging.debug("opensubtitlescom returned a non standard response. Logging request/response for debugging "
"purpose.")
else:
logging.debug("opensubtitlescom returned a standard response. Logging request/response for debugging purpose.")
logging.debug("Request URL: %s" % response.request.url)
logging.debug("Request Headers: %s" % redacted_request_headers)
logging.debug("Request Body: %s" % json.dumps(redacted_request_body))
logging.debug("Response Status Code: %s" % {response.status_code})
logging.debug("Response Headers: %s" % response.headers)
logging.debug("Response Body: %s" % json.dumps(redacted_response_body))
@@ -143,7 +143,7 @@ class SuperSubtitlesProvider(Provider, ProviderSubtitleArchiveMixin):
]}
video_types = (Episode, Movie)
# https://www.feliratok.info/?search=&soriSorszam=&nyelv=&sorozatnev=The+Flash+%282014%29&sid=3212&complexsearch=true&knyelv=0&evad=4&epizod1=1&cimke=0&minoseg=0&rlsr=0&tab=all
server_url = 'https://www.feliratok.info/'
server_url = 'https://www.feliratok.eu/'
subtitle_class = SuperSubtitlesSubtitle
hearing_impaired_verifiable = False
multi_result_throttle = 2 # seconds
+27 -8
View File
@@ -1,9 +1,11 @@
# coding=utf-8
from __future__ import absolute_import
import types
import re
from babelfish.exceptions import LanguageError
from babelfish import Language as Language_, basestr, LANGUAGE_MATRIX
from six.moves import zip
repl_map = {
"dk": "da",
@@ -30,11 +32,15 @@ repl_map = {
"tib": "bo",
}
CUSTOM_LIST = ["chs", "sc", "zhs", "hans", "gb", u"", u"双语",
"cht", "tc", "zht", "hant", "big5", u"", u"雙語",
"spl", "ea", "pob", "pb"]
ALPHA2_LIST = list(set(filter(lambda x: x, map(lambda x: x.alpha2, LANGUAGE_MATRIX)))) + list(repl_map.values())
ALPHA3b_LIST = list(set(filter(lambda x: x, map(lambda x: x.alpha3, LANGUAGE_MATRIX)))) + \
list(set(filter(lambda x: len(x) == 3, list(repl_map.keys()))))
FULL_LANGUAGE_LIST = ALPHA2_LIST + ALPHA3b_LIST
FULL_LANGUAGE_LIST.extend(CUSTOM_LIST)
def language_from_stream(l):
@@ -61,7 +67,8 @@ def wrap_forced(f):
args = args[1:]
s = args.pop(0)
forced = None
if isinstance(s, types.StringTypes):
hi = None
if isinstance(s, (str,)):
base, forced = s.split(":") if ":" in s else (s, False)
else:
base = s
@@ -69,6 +76,7 @@ def wrap_forced(f):
instance = f(cls, base, *args, **kwargs)
if isinstance(instance, Language):
instance.forced = forced == "forced"
instance.hi = hi == "hi"
return instance
return inner
@@ -76,16 +84,21 @@ def wrap_forced(f):
class Language(Language_):
forced = False
hi = False
def __init__(self, language, country=None, script=None, unknown=None, forced=False):
def __init__(self, language, country=None, script=None, unknown=None, forced=False, hi=False):
self.forced = forced
self.hi = hi
super(Language, self).__init__(language, country=country, script=script, unknown=unknown)
def __getstate__(self):
return self.alpha3, self.country, self.script, self.forced
return self.alpha3, self.country, self.script, self.hi, self.forced
def __setstate__(self, state):
self.alpha3, self.country, self.script, self.forced = state
def __setstate__(self, forced):
self.alpha3, self.country, self.script, self.hi, self.forced = forced
def __hash__(self):
return hash(str(self))
def __eq__(self, other):
if isinstance(other, basestr):
@@ -95,11 +108,16 @@ class Language(Language_):
return (self.alpha3 == other.alpha3 and
self.country == other.country and
self.script == other.script and
bool(self.forced) == bool(other.forced))
bool(self.forced) == bool(other.forced) and
bool(self.hi) == bool(other.hi))
def __str__(self):
return super(Language, self).__str__() + (":forced" if self.forced else "")
def __repr__(self):
info = ";".join("{}={}".format(k, v) for k, v in vars(self).items() if v)
return "<{}: {}>".format(self.__class__.__name__, info)
@property
def basename(self):
return super(Language, self).__str__()
@@ -108,14 +126,15 @@ class Language(Language_):
ret = super(Language, self).__getattr__(name)
if isinstance(ret, Language):
ret.forced = self.forced
ret.hi = self.hi
return ret
@classmethod
def rebuild(cls, instance, **replkw):
state = instance.__getstate__()
attrs = ("country", "script", "forced")
attrs = ("country", "script", "hi", "forced")
language = state[0]
kwa = dict(zip(attrs, state[1:]))
kwa = dict(list(zip(attrs, state[1:])))
kwa.update(replkw)
return cls(language, **kwa)
@@ -181,6 +181,14 @@ class FixUppercase(SubtitleModification):
entry.plaintext = self.capitalize(entry.plaintext)
"""
subsync
subsync --cli --offline --overwrite --window-size=600 --max-point-dist=2.0 --min-points-no=20 --min-word-prob=0.3 --min-word-len=5 --min-correlation=0.9999 --min-words-sim=0.6 --out-time-offset=-0.08 sync --out SUBTITLE -s SUBTITLE --sub-lang=eng --sub-enc=utf-8 -r REF_FILE --ref-stream-by-type=audio --ref-stream-by-lang=eng
"""
registry.register(CommonFixes)
registry.register(RemoveTags)
registry.register(ReverseRTL)
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -26,7 +26,7 @@ Don't expect support if you mess this up.
// SZ can use mediainfo if present to detect titles/forced state of MP4 MOV_TEXT, because the PMS currently doesn't
// set the title attribute
"dont_use_mediainfo_mp4": False,
"dont_use_mediainfo_mp4": false,
// specific mediainfo binary path
"mediainfo_bin": null,
@@ -89,11 +89,11 @@ Don't expect support if you mess this up.
// don't verify HTTPS certificates? Set to True for self-signed certificates
"ssl_no_verify": false,
// custom path to certificate pem file
"pem_file": None,
"pem_file": null,
},
"radarr": {
"ssl_no_verify": false,
"pem_file": None,
"pem_file": null,
},
}
}
+26 -2
View File
@@ -1,7 +1,7 @@
# Sub-Zero for Plex
[![](https://img.shields.io/github/release/pannal/Sub-Zero.bundle.svg?style=flat&label=stable)](https://github.com/pannal/Sub-Zero.bundle/releases/latest)
[![master](https://img.shields.io/badge/master-stable-green.svg?maxAge=2592000)]()
[![Maintenance](https://img.shields.io/maintenance/yes/2021.svg)]()
[![Maintenance](https://img.shields.io/maintenance/yes/2024.svg)]()
[![Slack Status](https://szslack.fragstore.net/badge.svg)](https://szslack.fragstore.net)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpannal%2FSub-Zero.bundle.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpannal%2FSub-Zero.bundle?ref=badge_shield)
@@ -12,6 +12,11 @@ Check out **[the Sub-Zero Wiki](https://github.com/pannal/Sub-Zero.bundle/wiki)*
<br />
# DEPRECATED, USE [BAZARR](https://www.bazarr.media/)
## Legacy maintenance mode
This addon will not be developed any further. It still works and arguably is still the best for managing subtitles when using Plex. As long as Plex Inc. supports agents, Sub-Zero will be maintained to work with the latest PMS version.
---
**[Kitana is now required to have a UI](https://github.com/pannal/Kitana)**
@@ -24,7 +29,7 @@ Check out **[the Sub-Zero Wiki](https://github.com/pannal/Sub-Zero.bundle/wiki)*
## Helping development
If you like this, buy me a beer: <br>[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G9VKR2B8PMNKG) <br>or become a Patreon starting at **1 $ / month** <br><a href="https://www.patreon.com/subzero_plex" target="_blank"><img src="https://i0.wp.com/tablecakes.com/wp-content/uploads/2018/08/become-a-patron-button.png" height="54" /></a> <br>or use the OpenSubtitles Sub-Zero affiliate link to become VIP <br>**10€/year, ad-free subs, 1000 subs/day, no-cache *VIP* server**<br><a href="http://v.ht/osvip" target="_blank"><img src="https://static.opensubtitles.org/gfx/logo.gif" height="50" /></a>
If you like this, buy me a beer: <br>[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G9VKR2B8PMNKG) <br>or become a Patreon starting at **1 $ / month** <br><a href="https://www.patreon.com/subzero_plex" target="_blank"><img src="https://images.squarespace-cdn.com/content/v1/56c7831bf8baf3ae17ce9259/1561826418792-IJOMFASTOR6CW80N5W0Z/ke17ZwdGBToddI8pDm48kEycOuEejcFJqqLot0yQ4VVZw-zPPgdn4jUwVcJE1ZvWEtT5uBSRWt4vQZAgTJucoTqqXjS3CfNDSuuf31e0tVFrfGYmOrPFZFUXr1UxW4wA0PgPOjs31URy2JeWL9DdYhur-lC0WofN0YB1wFg-ZW0/footer-patreon.png?format=500w" height="54" /></a>
If you register with an anti-captcha service and you decide to use [Anti-Captcha.com](http://getcaptchasolution.com/kkvviom7nh), you can use [this affiliate link](http://getcaptchasolution.com/kkvviom7nh) to help development.
@@ -93,6 +98,25 @@ the.vbm, mmgoodnow, Vertig0ne, thliu78, tattoomees, ostman, count_confucius, ehe
## Changelog
2.6.5.3280
temporarily enable OpenSubtitles.com instead of OpenSubtitles.org.
You need to have an account there and an API consumer configured. Enter your API key in settings.
This is barely tested but should work for basic usage.
THIS PLUGIN IS DEPRECATED, PLEASE USE BAZARR!
Changelog
- cheaply backport opensubtitlescom from bazarr
2.6.5.3277
- core: fix enabled library/agents detection (Plex removed certain features)
fix Plex agent integration; Plex Inc removed certain attributes; SZ is now limited to thetvdb, thetvdbdvdorder, hama, themoviedb, imdb)
2.6.5.3268
subscene, addic7ed
- 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