Compare commits

...

37 Commits

Author SHA1 Message Date
panni 51e87bdda5 don't crash the menu when no mods are applied on the current subtitle 2017-05-06 18:07:53 +02:00
panni f88677b0f6 fix common fixes description 2017-05-06 18:04:20 +02:00
panni fc71ec0250 remove unnecessary debounces 2017-05-06 18:00:40 +02:00
panni ca6089c220 Pre-Release 2.0.12.1180 RC1 2017-05-06 17:49:58 +02:00
panni 7cc051fd90 set default movie score to lowest (60) 2017-05-06 17:43:38 +02:00
panni 5b01fda526 adapt forced_only for new providers (disable them) 2017-05-06 17:37:31 +02:00
panni 585f6b8a4d rename config.use_activities to react_to_activities and act accordingly 2017-05-06 17:29:11 +02:00
panni 81aeba0874 use added icon instead of recent icon for recently added menu 2017-05-06 17:24:05 +02:00
panni d9133e2793 add recently played menu 2017-05-06 17:22:33 +02:00
panni 9ef740ae1f remove_HI: less aggressive bracket content matching 2017-05-06 16:53:32 +02:00
panni e54fe71e93 reduce addicted default boost to 21 2017-05-06 16:46:54 +02:00
panni 9df878b8e3 add common fixes as default; remove debug print 2017-05-06 16:46:22 +02:00
panni 1a59c267c1 remove doublequote processors, doesn't seem possible 2017-05-06 16:42:07 +02:00
panni f8a07d983b fix typo resolves #274 2017-05-06 15:28:40 +02:00
panni 1f1847f246 change doublequote regexes 2017-05-06 06:48:52 +02:00
panni a32dfd6b37 add common fixes 2017-05-06 06:14:58 +02:00
panni b1cce92e04 use positive lookahead for HI all caps line detection 2017-05-06 01:35:43 +02:00
panni fdf32439c9 don't remove dash-in-front on hearing impaired; skip empty lines properly 2017-05-06 01:26:17 +02:00
panni fc2208f9e5 bump version 2017-05-05 19:32:12 +02:00
panni 1a4eb366bb add helping indicator to FPS mod; add 30fps 2017-05-05 19:31:43 +02:00
panni b89c64a2c2 add modification management menu 2017-05-05 19:19:34 +02:00
panni 68e8f6e753 don't remove HI by default 2017-05-05 19:11:43 +02:00
panni f15cc4cb3c add offset shifter submod 2017-05-05 19:10:32 +02:00
panni 903273e3ef add advanced submods; add global (non-line) submods; test implementation of ChangeFPS mod 2017-05-05 15:39:18 +02:00
panni 1c9b744d31 move subtitle modification menu to separate file 2017-05-05 14:58:19 +02:00
panni 7c0fb29886 fix init_cache whoopsie 2017-05-05 14:58:06 +02:00
panni 2505a7510c enzyme: incorporate 0.4.2 fixes 2017-05-05 14:44:59 +02:00
panni 0a66db40a2 fix findbetter 2017-05-05 14:30:49 +02:00
panni 6c68893979 add mod.long_description; add remove_last action to subtitle modification menu 2017-05-04 20:10:35 +02:00
panni c512eab0b6 testcommit 2017-05-04 20:00:12 +02:00
panni 3cedd4bd0f try getting plex token from environment by default 2017-05-04 19:33:05 +02:00
panni 0759c5e4c6 add environment debug 2017-05-04 19:31:07 +02:00
panni ad6cf4be79 move config debug to better position; verify readability of log files 2017-05-04 19:15:38 +02:00
panni 23c3899fb2 add fixme 2017-05-04 14:30:25 +02:00
panni 1a6515a660 add platform and os to config debug 2017-05-04 14:20:29 +02:00
panni 58815a7650 use external ip fallback when logs were requested from plex.tv 2017-05-04 14:16:10 +02:00
panni c15ec9fefc disable get_logs when universal plex token is None 2017-05-04 13:49:02 +02:00
30 changed files with 596 additions and 150 deletions
-1
View File
@@ -24,7 +24,6 @@ import support
import interface
sys.modules["interface"] = interface
from subliminal.cli import MutexLock
from subzero.constants import OS_PLEX_USERAGENT, PERSONAL_MEDIA_IDENTIFIER
from interface.menu import *
from support.plex_media import media_to_videos, get_media_item_ids, scan_videos
+3
View File
@@ -18,3 +18,6 @@ sys.modules["interface.refresh_item"] = refresh_item
import item_details
sys.modules["interface.item_details"] = item_details
import sub_mod
sys.modules["interface.modification"] = sub_mod
+12 -2
View File
@@ -148,24 +148,34 @@ def TriggerStorageMaintenance(randomize=None):
@route(PREFIX + '/get_logs_link')
def GetLogsLink():
if not config.plex_token:
oc = ObjectContainer(title2="Download Logs", no_cache=True, no_history=True,
header="Sorry, feature unavailable",
message="Universal Plex token not available")
return oc
# try getting the link base via the request in context, first, otherwise use the public ip
req_headers = Core.sandbox.context.request.headers
get_external_ip = True
link_base = ""
if "Origin" in req_headers:
link_base = req_headers["Origin"]
Log.Debug("Using origin-based link_base")
get_external_ip = False
elif "Referer" in req_headers:
parsed = urlparse.urlparse(req_headers["Referer"])
link_base = "%s://%s:%s" % (parsed.scheme, parsed.hostname, parsed.port)
Log.Debug("Using referer-based link_base")
get_external_ip = False
else:
if get_external_ip or "plex.tv" in link_base:
ip = Core.networking.http_request("http://www.plexapp.com/ip.php", cacheTime=7200).content.strip()
link_base = "https://%s:32400" % ip
Log.Debug("Using ip-based fallback link_base")
logs_link = "%s%s?X-Plex-Token=%s" % (link_base, PREFIX + '/logs', config.universal_plex_token)
logs_link = "%s%s?X-Plex-Token=%s" % (link_base, PREFIX + '/logs', config.plex_token)
oc = ObjectContainer(title2="Download Logs", no_cache=True, no_history=True,
header="Copy this link and open this in your browser, please",
message=logs_link)
+3 -68
View File
@@ -1,13 +1,11 @@
# coding=utf-8
import os
import traceback
from babelfish import Language
from sub_mod import SubtitleModificationsMenu
from menu_helpers import debounce, SubFolderObjectContainer, default_thumb, add_ignore_options, get_item_task_data, \
set_refresh_menu_state
from subzero.modification import registry as mod_registry
from refresh_item import RefreshItem
from subliminal_patch import PatchedSubtitle as Subtitle
from subzero.constants import PREFIX
from support.config import config
from support.helpers import timestamp, cast_bool, df, get_language
@@ -15,7 +13,7 @@ from support.items import get_item_kind_from_rating_key, get_item, get_current_s
from support.lib import Plex
from support.plex_media import get_plex_metadata, scan_videos
from support.scheduler import scheduler
from support.storage import get_subtitle_storage, save_subtitles
from support.storage import get_subtitle_storage
@route(PREFIX + '/item/{rating_key}/actions')
@@ -139,69 +137,6 @@ def SubtitleOptionsMenu(**kwargs):
return oc
@route(PREFIX + '/item/sub_mods/{rating_key}/{part_id}', force=bool)
@debounce
def SubtitleModificationsMenu(**kwargs):
rating_key = kwargs["rating_key"]
part_id = kwargs["part_id"]
language = kwargs["language"]
current_sub, stored_subs, storage = get_current_sub(rating_key, part_id, language)
kwargs.pop("randomize")
oc = SubFolderObjectContainer(title2=kwargs["title"], replace_parent=True)
for identifier, mod in mod_registry.mods.iteritems():
oc.add(DirectoryObject(
key=Callback(SubtitleApplyMod, mod_identifier=identifier, randomize=timestamp(), **kwargs),
title=mod.description
))
oc.add(DirectoryObject(
key=Callback(SubtitleApplyMod, mod_identifier=None, randomize=timestamp(), **kwargs),
title="Restore original version",
summary=u"Currently applied mods: %s" % (", ".join(current_sub.mods) if current_sub.mods else "none")
))
return oc
@route(PREFIX + '/item/sub_add_mod/{rating_key}/{part_id}/{mod_identifier}', force=bool)
@debounce
def SubtitleApplyMod(mod_identifier=None, **kwargs):
if mod_identifier is not None and mod_identifier not in mod_registry.mods:
raise NotImplementedError
rating_key = kwargs["rating_key"]
part_id = kwargs["part_id"]
lang_a2 = kwargs["language"]
item_type = kwargs["item_type"]
language = Language.fromietf(lang_a2)
current_sub, stored_subs, storage = get_current_sub(rating_key, part_id, language)
current_sub.add_mod(mod_identifier)
storage.save(stored_subs)
metadata = get_plex_metadata(rating_key, part_id, item_type)
scanned_parts = scan_videos([metadata], kind="series" if item_type == "episode" else "movie", ignore_all=True)
video, plex_part = scanned_parts.items()[0]
subtitle = Subtitle(language, mods=current_sub.mods)
subtitle.content = current_sub.content
subtitle.plex_media_fps = plex_part.fps
subtitle.page_link = "modify subtitles with: %s" % (", ".join(current_sub.mods) if current_sub.mods else "none")
subtitle.language = language
try:
save_subtitles(scanned_parts, {video: [subtitle]}, mode="m", bare_save=True)
Log.Debug("Modified %s subtitle for: %s:%s with: %s", language.name, rating_key, part_id,
", ".join(current_sub.mods) if current_sub.mods else "none")
except:
Log.Error("Something went wrong when modifying subtitle: %s", traceback.format_exc())
kwargs.pop("randomize")
return SubtitleModificationsMenu(randomize=timestamp(), **kwargs)
@route(PREFIX + '/item/search/{rating_key}/{part_id}', force=bool)
@debounce
def ListAvailableSubsForItemMenu(rating_key=None, part_id=None, title=None, item_title=None, filename=None,
+37 -3
View File
@@ -2,10 +2,11 @@
from subzero.constants import PREFIX, TITLE, ART
from support.config import config
from support.helpers import pad_title, timestamp, df
from support.helpers import pad_title, timestamp, df, get_plex_item_display_title
from support.scheduler import scheduler
from support.ignore import ignore_list
from support.items import get_item_thumb, get_on_deck_items, get_all_items, get_items_info
from support.items import get_item_thumb, get_on_deck_items, get_all_items, get_items_info, get_item, \
get_item_kind_from_item
from menu_helpers import main_icon, debounce, SubFolderObjectContainer, default_thumb, dig_tree, add_ignore_options
from item_details import ItemDetailsMenu
@@ -74,11 +75,19 @@ def fatality(randomize=None, force_title=None, header=None, message=None, only_r
"subtitles.",
thumb=R("icon-ondeck.jpg")
))
if "last_played_items" in Dict and Dict["last_played_items"]:
oc.add(DirectoryObject(
key=Callback(RecentlyPlayedMenu),
title=pad_title("Recently played items"),
summary="Shows the 10 recently played items and allows you to individually (force-) refresh their "
"metadata/subtitles.",
thumb=R("icon-played.jpg")
))
oc.add(DirectoryObject(
key=Callback(RecentlyAddedMenu),
title="Recently Added items",
summary="Shows the recently added items per section.",
thumb=R("icon-recent.jpg")
thumb=R("icon-added.jpg")
))
oc.add(DirectoryObject(
key=Callback(RecentMissingSubtitlesMenu, randomize=timestamp()),
@@ -168,6 +177,31 @@ def OnDeckMenu(message=None):
return mergedItemsMenu(title="Items On Deck", base_title="Items On Deck", itemGetter=get_on_deck_items)
@route(PREFIX + '/recently_played')
def RecentlyPlayedMenu():
base_title = "Recently Played"
oc = SubFolderObjectContainer(title2=base_title, replace_parent=True)
for item in [get_item(rating_key) for rating_key in Dict["last_played_items"]]:
kind = get_item_kind_from_item(item)
if kind not in ("episode", "movie"):
continue
if kind == "episode":
item_title = get_plex_item_display_title(item, "show", parent=item.season, section_title=None,
parent_title=item.show.title)
else:
item_title = get_plex_item_display_title(item, kind, section_title=None)
oc.add(DirectoryObject(
title=item_title,
key=Callback(ItemDetailsMenu, title=base_title + " > " + item.title, item_title=item.title,
rating_key=item.rating_key)
))
return oc
@route(PREFIX + '/recently_added')
def RecentlyAddedMenu(message=None):
"""
+38 -10
View File
@@ -1,5 +1,7 @@
# coding=utf-8
import logging
import os
import logger
from item_details import ItemDetailsMenu
@@ -147,16 +149,6 @@ def RefreshMissing(randomize=None):
@route(PREFIX + '/ValidatePrefs', enforce_route=True)
def ValidatePrefs():
Core.log.setLevel(logging.DEBUG)
Log.Debug("Validate Prefs called.")
# SZ config debug
Log.Debug("--- SZ Config-Debug ---")
for attr in [
"app_support_path", "data_path", "data_items_path", "plugin_log_path", "server_log_path", "enable_agent",
"enable_channel", "permissions_ok", "missing_permissions", "fs_encoding"]:
Log.Debug("config.%s: %s", attr, getattr(config, attr))
Log.Debug("-----------------------")
# cache the channel state
update_dict = False
@@ -191,6 +183,42 @@ def ValidatePrefs():
Core.log.removeHandler(logger.console_handler)
Log.Debug("Stop logging to console")
Log.Debug("Validate Prefs called.")
# SZ config debug
Log.Debug("--- SZ Config-Debug ---")
for attr in [
"app_support_path", "data_path", "data_items_path", "enable_agent",
"enable_channel", "permissions_ok", "missing_permissions", "fs_encoding"]:
Log.Debug("config.%s: %s", attr, getattr(config, attr))
for attr in ["plugin_log_path", "server_log_path"]:
value = getattr(config, attr)
access = os.access(value, os.R_OK)
if Core.runtime.os == "Windows":
try:
f = open(value, "r")
f.read(1)
f.close()
except:
access = False
Log.Debug("config.%s: %s (accessible: %s)", attr, value, access)
# fixme: check existance of and os access of logs
Log.Debug("Platform: %s", Core.runtime.platform)
Log.Debug("OS: %s", Core.runtime.os)
Log.Debug("----- Environment -----")
for key, value in os.environ.iteritems():
if key.startswith("PLEX"):
if "TOKEN" in key:
outval = "xxxxxxxxxxxxxxxxxxx"
else:
outval = value
Log.Debug("%s: %s", key, outval)
Log.Debug("-----------------------")
Log.Debug("Setting log-level to %s", Prefs["log_level"])
logger.register_logging_handler(DEPENDENCY_MODULE_NAMES, level=Prefs["log_level"])
Core.log.setLevel(logging.getLevelName(Prefs["log_level"]))
+230
View File
@@ -0,0 +1,230 @@
# coding=utf-8
import traceback
import types
from babelfish import Language
from menu_helpers import debounce, SubFolderObjectContainer
from subliminal_patch import PatchedSubtitle as Subtitle
from subzero.modification import registry as mod_registry, SubtitleModifications
from subzero.constants import PREFIX
from support.plex_media import get_plex_metadata, scan_videos
from support.storage import save_subtitles
from support.helpers import timestamp, pad_title
from support.items import get_current_sub
@route(PREFIX + '/item/sub_mods/{rating_key}/{part_id}', force=bool)
@debounce
def SubtitleModificationsMenu(**kwargs):
rating_key = kwargs["rating_key"]
part_id = kwargs["part_id"]
language = kwargs["language"]
current_sub, stored_subs, storage = get_current_sub(rating_key, part_id, language)
kwargs.pop("randomize")
current_mods = current_sub.mods or []
oc = SubFolderObjectContainer(title2=kwargs["title"], replace_parent=True)
for identifier, mod in mod_registry.mods.iteritems():
if mod.advanced:
continue
if mod.exclusive and identifier in current_mods:
continue
oc.add(DirectoryObject(
key=Callback(SubtitleSetMods, mods=identifier, mode="add", randomize=timestamp(), **kwargs),
title=pad_title(mod.description), summary=mod.long_description or ""
))
fps_mod = SubtitleModifications.get_mod_class("change_FPS")
oc.add(DirectoryObject(
key=Callback(SubtitleFPSModMenu, randomize=timestamp(), **kwargs),
title=pad_title(fps_mod.description), summary=fps_mod.long_description or ""
))
shift_mod = SubtitleModifications.get_mod_class("shift_offset")
oc.add(DirectoryObject(
key=Callback(SubtitleShiftModUnitMenu, randomize=timestamp(), **kwargs),
title=pad_title(shift_mod.description), summary=shift_mod.long_description or ""
))
if current_mods:
oc.add(DirectoryObject(
key=Callback(SubtitleSetMods, mods=None, mode="remove_last", randomize=timestamp(), **kwargs),
title=pad_title("Remove last applied mod (%s)" % current_mods[-1]),
summary=u"Currently applied mods: %s" % (", ".join(current_mods) if current_mods else "none")
))
oc.add(DirectoryObject(
key=Callback(SubtitleListMods, randomize=timestamp(), **kwargs),
title=pad_title("Manage applied mods"),
summary=u"Currently applied mods: %s" % (", ".join(current_mods))
))
oc.add(DirectoryObject(
key=Callback(SubtitleSetMods, mods=None, mode="clear", randomize=timestamp(), **kwargs),
title=pad_title("Restore original version"),
summary=u"Currently applied mods: %s" % (", ".join(current_mods) if current_mods else "none")
))
return oc
@route(PREFIX + '/item/sub_mod_fps/{rating_key}/{part_id}', force=bool)
def SubtitleFPSModMenu(**kwargs):
rating_key = kwargs["rating_key"]
part_id = kwargs["part_id"]
item_type = kwargs["item_type"]
oc = SubFolderObjectContainer(title2=kwargs["title"], replace_parent=True)
metadata = get_plex_metadata(rating_key, part_id, item_type)
scanned_parts = scan_videos([metadata], kind="series" if item_type == "episode" else "movie", ignore_all=True)
video, plex_part = scanned_parts.items()[0]
target_fps = plex_part.fps
kwargs.pop("randomize")
for fps in ["23.976", "24.000", "25.000", "29.970", "30.000", "50.000", "59.940", "60.000"]:
if float(fps) == float(target_fps):
continue
if float(fps) > float(target_fps):
indicator = "subs constantly getting faster"
else:
indicator = "subs constantly getting slower"
mod_ident = SubtitleModifications.get_mod_signature("change_FPS", **{"from": fps, "to": target_fps})
oc.add(DirectoryObject(
key=Callback(SubtitleSetMods, mods=mod_ident, mode="add", randomize=timestamp(), **kwargs),
title="%s fps -> %s fps (%s)" % (fps, target_fps, indicator)
))
return oc
POSSIBLE_UNITS = (("ms", "milliseconds"), ("s", "seconds"), ("m", "minutes"), ("h", "hours"))
POSSIBLE_UNITS_D = dict(POSSIBLE_UNITS)
@route(PREFIX + '/item/sub_mod_shift_unit/{rating_key}/{part_id}', force=bool)
def SubtitleShiftModUnitMenu(**kwargs):
oc = SubFolderObjectContainer(title2=kwargs["title"], replace_parent=True)
kwargs.pop("randomize")
for unit, title in POSSIBLE_UNITS:
oc.add(DirectoryObject(
key=Callback(SubtitleShiftModMenu, unit=unit, randomize=timestamp(), **kwargs),
title="Adjust by %s" % title
))
return oc
@route(PREFIX + '/item/sub_mod_shift/{rating_key}/{part_id}/{unit}', force=bool)
def SubtitleShiftModMenu(unit=None, **kwargs):
if unit not in POSSIBLE_UNITS_D:
raise NotImplementedError
oc = SubFolderObjectContainer(title2=kwargs["title"], replace_parent=True)
kwargs.pop("randomize")
rng = []
if unit == "h":
rng = range(-10, 11)
elif unit in ("m", "s"):
rng = range(-59, 60)
elif unit == "ms":
rng = range(-900, 1000, 100)
for i in rng:
if i == 0:
continue
mod_ident = SubtitleModifications.get_mod_signature("shift_offset", **{unit: i})
oc.add(DirectoryObject(
key=Callback(SubtitleSetMods, mods=mod_ident, mode="add", randomize=timestamp(), **kwargs),
title="%s %s" % (("%s" if i < 0 else "+%s") % i, unit)
))
return oc
@route(PREFIX + '/item/sub_set_mods/{rating_key}/{part_id}/{mods}/{mode}', force=bool)
@debounce
def SubtitleSetMods(mods=None, mode=None, **kwargs):
if not isinstance(mods, types.ListType) and mods:
mods = [mods]
rating_key = kwargs["rating_key"]
part_id = kwargs["part_id"]
lang_a2 = kwargs["language"]
item_type = kwargs["item_type"]
language = Language.fromietf(lang_a2)
current_sub, stored_subs, storage = get_current_sub(rating_key, part_id, language)
if mode == "add":
for mod in mods:
identifier, args = SubtitleModifications.parse_identifier(mod)
if identifier not in mod_registry.mods_available:
raise NotImplementedError("Mod unknown or not registered")
current_sub.add_mod(mod)
elif mode == "clear":
current_sub.add_mod(None)
elif mode == "remove":
for mod in mods:
current_sub.mods.remove(mod)
elif mode == "remove_last":
if current_sub.mods:
current_sub.mods.pop()
else:
raise NotImplementedError("Wrong mode given")
storage.save(stored_subs)
metadata = get_plex_metadata(rating_key, part_id, item_type)
scanned_parts = scan_videos([metadata], kind="series" if item_type == "episode" else "movie", ignore_all=True)
video, plex_part = scanned_parts.items()[0]
subtitle = Subtitle(language, mods=current_sub.mods)
subtitle.content = current_sub.content
subtitle.plex_media_fps = plex_part.fps
subtitle.page_link = "modify subtitles with: %s" % (", ".join(current_sub.mods) if current_sub.mods else "none")
subtitle.language = language
try:
save_subtitles(scanned_parts, {video: [subtitle]}, mode="m", bare_save=True)
Log.Debug("Modified %s subtitle for: %s:%s with: %s", language.name, rating_key, part_id,
", ".join(current_sub.mods) if current_sub.mods else "none")
except:
Log.Error("Something went wrong when modifying subtitle: %s", traceback.format_exc())
kwargs.pop("randomize")
return SubtitleModificationsMenu(randomize=timestamp(), **kwargs)
@route(PREFIX + '/item/sub_list_mods/{rating_key}/{part_id}', force=bool)
@debounce
def SubtitleListMods(**kwargs):
rating_key = kwargs["rating_key"]
part_id = kwargs["part_id"]
language = kwargs["language"]
current_sub, stored_subs, storage = get_current_sub(rating_key, part_id, language)
kwargs.pop("randomize")
oc = SubFolderObjectContainer(title2=kwargs["title"], replace_parent=True)
for identifier in current_sub.mods:
oc.add(DirectoryObject(
key=Callback(SubtitleSetMods, mods=identifier, mode="remove", randomize=timestamp(), **kwargs),
title="Remove: %s" % identifier
))
return oc
+5 -5
View File
@@ -11,9 +11,9 @@ class PlexActivityManager(object):
def start(self):
activity_sources_enabled = None
if config.universal_plex_token:
if config.plex_token:
from plex import Plex
Plex.configuration.defaults.authentication(config.universal_plex_token)
Plex.configuration.defaults.authentication(config.plex_token)
activity_sources_enabled = ["websocket"]
Activity.on('websocket.playing', self.on_playing)
@@ -27,9 +27,6 @@ class PlexActivityManager(object):
@throttle(5, instance_method=True)
def on_playing(self, info):
if not config.use_activities:
return
# ignore non-playing states and anything too far in
if info["state"] != "playing" or info["viewOffset"] > 60000:
return
@@ -48,6 +45,9 @@ class PlexActivityManager(object):
Dict.Save()
if not config.react_to_activities:
return
debug_msg = "Started playing %s. Refreshing it." % rating_key
key_to_refresh = None
+15 -5
View File
@@ -9,6 +9,7 @@ import datetime
import subliminal
import subliminal_patch
from babelfish import Language
from subliminal.cli import MutexLock
from subzero.lib.io import FileIO, get_viable_encoding
from subzero.constants import PLUGIN_NAME, PLUGIN_IDENTIFIER, MOVIE, SHOW
from lib import Plex
@@ -45,6 +46,7 @@ class Config(object):
data_path = None
data_items_path = None
universal_plex_token = None
plex_token = None
is_development = False
enable_channel = True
@@ -69,6 +71,7 @@ class Config(object):
enabled_sections = None
remove_hi = False
fix_ocr = False
fix_common = False
enforce_encoding = False
chmod = None
forced_only = False
@@ -76,7 +79,7 @@ class Config(object):
treat_und_as_first = False
ext_match_strictness = False
default_mods = None
use_activities = False
react_to_activities = False
activity_mode = None
initialized = False
@@ -92,6 +95,7 @@ class Config(object):
self.data_path = getattr(Data, "_core").storage.data_path
self.data_items_path = os.path.join(self.data_path, "DataItems")
self.universal_plex_token = self.get_universal_plex_token()
self.plex_token = os.environ.get("PLEXTOKEN", self.universal_plex_token)
self.set_plugin_mode()
self.set_plugin_lock()
@@ -99,6 +103,7 @@ class Config(object):
self.lang_list = self.get_lang_list()
self.subtitle_destination_folder = self.get_subtitle_destination_folder()
self.forced_only = cast_bool(Prefs["subtitles.only_foreign"])
self.providers = self.get_providers()
self.provider_settings = self.get_provider_settings()
self.max_recent_items_per_library = int_or_default(Prefs["scheduler.max_recent_items_per_library"], 2000)
@@ -111,9 +116,9 @@ class Config(object):
self.notify_executable = self.check_notify_executable()
self.remove_hi = cast_bool(Prefs['subtitles.remove_hi'])
self.fix_ocr = cast_bool(Prefs['subtitles.fix_ocr'])
self.fix_common = cast_bool(Prefs['subtitles.fix_common'])
self.enforce_encoding = cast_bool(Prefs['subtitles.enforce_encoding'])
self.chmod = self.check_chmod()
self.forced_only = cast_bool(Prefs["subtitles.only_foreign"])
self.exotic_ext = cast_bool(Prefs["subtitles.scan.exotic_ext"])
self.treat_und_as_first = cast_bool(Prefs["subtitles.language.treat_und_as_first"])
self.ext_match_strictness = self.determine_ext_sub_strictness()
@@ -161,6 +166,8 @@ class Config(object):
else:
Log("Did NOT find Preferences file - please check logfile and hierarchy. Aborting!")
# fixme: windows
def set_plugin_mode(self):
if Prefs["plugin_mode"] == "only agent":
self.enable_channel = False
@@ -364,10 +371,13 @@ class Config(object):
}
# ditch non-forced-subtitles-reporting providers
if cast_bool(Prefs['subtitles.only_foreign']):
if self.forced_only:
providers["addic7ed"] = False
providers["tvsubtitles"] = False
providers["legendastv"] = False
providers["napiprojekt"] = False
providers["shooter"] = False
providers["subscenter"] = False
return filter(lambda prov: providers[prov], providers)
@@ -437,10 +447,10 @@ class Config(object):
def set_activity_modes(self):
val = Prefs["activity.on_playback"]
if val == "never":
self.use_activities = False
self.react_to_activities = False
return
self.use_activities = True
self.react_to_activities = True
if val == "current media item":
self.activity_mode = "refresh"
elif val == "hybrid: current item or next episode":
+2 -2
View File
@@ -110,9 +110,9 @@ def str_pad(s, length, align='left', pad_char=' ', trim=False):
raise ValueError("Unknown align type, expected either 'left' or 'right'")
def pad_title(value):
def pad_title(value, width=49):
"""Pad a title to 30 characters to force the 'details' view."""
return str_pad(value, 49, pad_char=' ')
return str_pad(value, width, pad_char=' ')
def get_plex_item_display_title(item, kind, parent=None, parent_title=None, section_title=None,
+1
View File
@@ -374,6 +374,7 @@ class FindBetterSubtitles(DownloadSubtitleMixin, SubtitleListingMixin, Task):
video_id = stored_subs.video_id
if stored_subs.item_type == "episode":
cutoff = self.series_cutoff
min_score = min_score_series
else:
cutoff = self.movies_cutoff
+10 -3
View File
@@ -258,13 +258,14 @@
"35",
"30",
"25",
"21",
"20",
"15",
"10",
"5",
"0"
],
"default": "25"
"default": "21"
},
{
"id": "provider.addic7ed.use_random_agents",
@@ -332,7 +333,7 @@
},
{
"id": "providers.multithreading",
"label": "Search enabled providers simuntaneously (multithreading)",
"label": "Search enabled providers simultaneously (multithreading)",
"type": "bool",
"default": "true"
},
@@ -381,7 +382,7 @@
"id": "subtitles.search.minimumMovieScore2",
"label": "Minimum score for movies (min: 60, def/sane: 69, min-ideal: 82; see http://v.ht/szscores)",
"type": "text",
"default": "69"
"default": "60"
},
{
"id": "subtitles.search.hearingImpaired",
@@ -399,6 +400,12 @@
"id": "subtitles.remove_hi",
"label": "Remove Hearing Impaired tags from downloaded subtitles",
"type": "bool",
"default": "false"
},
{
"id": "subtitles.fix_common",
"label": "Fix common whitespace/punctuation issues in subtitles",
"type": "bool",
"default": "true"
},
{
+3 -3
View File
@@ -9,11 +9,11 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>2.0.0</string>
<string>2.0.12</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.0.0.12</string>
<string>2.0.12.1180</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.0.0.12 DEV
Version 2.0.12.1180 RC1
Originally based on @bramwalet's awesome &lt;a href=&quot;https://github.com/bramwalet/Subliminal.bundle&quot;&gt;Subliminal.bundle&lt;/a&gt;
+2 -1
View File
@@ -369,7 +369,8 @@ class Chapter(object):
if chapterdisplays:
string = chapterdisplays[0].get('ChapString')
language = chapterdisplays[0].get('ChapLanguage')
return cls(start, hidden, enabled, end, string, language)
return cls(start, hidden, enabled, end, string, language)
return cls(start, hidden, enabled, end)
def __repr__(self):
return '<%s [%s, enabled=%s]>' % (self.__class__.__name__, self.start, self.enabled)
@@ -146,7 +146,7 @@ class PatchedSubtitle(Subtitle):
encoding = self.guess_encoding()
submods = SubtitleModifications()
submods.load(content=self.text, fps=self.plex_media_fps, language=self.language)
submods.load(content=self.text, language=self.language)
submods.modify(*self.mods)
return submods.to_string("srt", encoding=encoding).encode(encoding=encoding)
+4 -2
View File
@@ -16,8 +16,10 @@ if debug:
logging.basicConfig(level=logging.DEBUG)
submod = SubMod(debug=debug)
submod.load(fn, language=Language.fromietf("en"))
submod.modify("remove_HI", "OCR_fixes")
submod.load(fn, language=Language.fromietf("eng"))
submod.modify("remove_HI", "OCR_fixes", "common")
#submod.modify("OCR_fixes")
#submod.modify("change_FPS(from=24,to=25)")
#submod.modify("common")
#print submod.f.to_string("srt")
@@ -1,5 +1,5 @@
# coding=utf-8
from registry import registry
from mods import hearing_impaired, ocr_fixes
from mods import hearing_impaired, ocr_fixes, fps, offset, common
from main import SubtitleModifications, SubMod
@@ -18,22 +18,21 @@ class SubtitleModifications(object):
self.debug = debug
self.initialized_mods = {}
def load(self, fn=None, content=None, fps=None, language=None):
def load(self, fn=None, content=None, language=None):
"""
:param language: babelfish.Language language of the subtitle
:param fn: filename
:param content: unicode
:param fps:
:return:
"""
self.language = language
self.initialized_mods = {}
try:
if fn:
self.f = pysubs2.load(fn, fps=fps)
self.f = pysubs2.load(fn)
elif content:
self.f = pysubs2.SSAFile.from_string(content, fps=fps)
self.f = pysubs2.SSAFile.from_string(content)
except (IOError,
UnicodeDecodeError,
pysubs2.exceptions.UnknownFPSError,
@@ -44,19 +43,58 @@ class SubtitleModifications(object):
elif content:
logger.exception("Couldn't load subtitle: %s", traceback.format_exc())
@classmethod
def parse_identifier(cls, identifier):
# simple identifier
if identifier in registry.mods:
return identifier, {}
# identifier with params; identifier(param=value)
split_args = identifier[identifier.find("(")+1:-1].split(",")
args = dict((key, value) for key, value in [sub.split("=") for sub in split_args])
return identifier[:identifier.find("(")], args
@classmethod
def get_mod_class(cls, identifier):
identifier, args = cls.parse_identifier(identifier)
return registry.mods[identifier]
@classmethod
def get_mod_signature(cls, identifier, **kwargs):
return cls.get_mod_class(identifier).get_signature(**kwargs)
def modify(self, *mods):
new_f = []
for identifier in mods:
parsed_mods = [SubtitleModifications.parse_identifier(mod) for mod in mods]
line_mods = []
non_line_mods = []
for identifier, args in parsed_mods:
if identifier not in registry.mods:
raise NotImplementedError("Mod %s not loaded" % identifier)
for line in self.f:
applied_mods = []
for identifier in mods:
if identifier in registry.mods:
if identifier not in self.initialized_mods:
self.initialized_mods[identifier] = registry.mods[identifier](self)
mod_cls = registry.mods[identifier]
if mod_cls.modifies_whole_file:
non_line_mods.append((identifier, args))
else:
line_mods.append((identifier, args))
if identifier not in self.initialized_mods:
self.initialized_mods[identifier] = mod_cls(self)
# apply file mods
if non_line_mods:
for identifier, args in non_line_mods:
mod = self.initialized_mods[identifier]
mod.modify(None, debug=self.debug, parent=self, **args)
# apply line mods
if line_mods:
for line in self.f:
applied_mods = []
skip_line = False
for identifier, args in line_mods:
mod = self.initialized_mods[identifier]
# don't bother reapplying exclusive mods multiple times
@@ -66,15 +104,17 @@ class SubtitleModifications(object):
if not mod.processors:
continue
new_content = mod.modify(line.text, debug=self.debug, parent=self)
new_content = mod.modify(line.text, debug=self.debug, parent=self, **args)
if not new_content:
if self.debug:
logger.debug("%s: deleting %s", identifier, line)
continue
skip_line = True
break
line.text = new_content
applied_mods.append(identifier)
new_f.append(line)
if not skip_line:
new_f.append(line)
self.f.events = new_f
@@ -10,7 +10,10 @@ logger = logging.getLogger(__name__)
class SubtitleModification(object):
identifier = None
description = None
long_description = None
exclusive = False
advanced = False # has parameters
modifies_whole_file = False # operates on the whole file, not individual entries
pre_processors = []
processors = []
post_processors = []
@@ -18,7 +21,7 @@ class SubtitleModification(object):
def __init__(self, parent):
return
def _process(self, content, processors, debug=False, parent=None):
def _process(self, content, processors, debug=False, parent=None, **kwargs):
if not content:
return
@@ -43,22 +46,27 @@ class SubtitleModification(object):
logger.debug("%s: %s -> %s", processor, old_content, new_content)
return new_content
def pre_process(self, content, debug=False, parent=None):
return self._process(content, self.pre_processors, debug=debug, parent=parent)
def pre_process(self, content, debug=False, parent=None, **kwargs):
return self._process(content, self.pre_processors, debug=debug, parent=parent, **kwargs)
def process(self, content, debug=False, parent=None):
return self._process(content, self.processors, debug=debug, parent=parent)
def process(self, content, debug=False, parent=None, **kwargs):
return self._process(content, self.processors, debug=debug, parent=parent, **kwargs)
def post_process(self, content, debug=False, parent=None):
return self._process(content, self.post_processors, debug=debug, parent=parent)
def post_process(self, content, debug=False, parent=None, **kwargs):
return self._process(content, self.post_processors, debug=debug, parent=parent, **kwargs)
def modify(self, content, debug=False, parent=None):
def modify(self, content, debug=False, parent=None, **kwargs):
new_content = content
for method in ("pre_process", "process", "post_process"):
new_content = getattr(self, method)(new_content, debug=debug, parent=parent)
new_content = getattr(self, method)(new_content, debug=debug, parent=parent, **kwargs)
return new_content
@classmethod
def get_signature(cls, **kwargs):
string_args = ",".join(["%s=%s" % (key, value) for key, value in kwargs.iteritems()])
return "%s(%s)" % (cls.identifier, string_args)
class SubtitleTextModification(SubtitleModification):
post_processors = [
@@ -0,0 +1,58 @@
# coding=utf-8
import re
from subzero.modification.mods import SubtitleTextModification
from subzero.modification.processors.string_processor import StringProcessor
from subzero.modification.processors.re_processor import NReProcessor
from subzero.modification import registry
class CommonFixes(SubtitleTextModification):
identifier = "common"
description = "Basic common fixes"
exclusive = True
long_description = """\
Fix common whitespace/punctuation issues in subtitles
"""
processors = [
# no space after ellipsis
NReProcessor(re.compile(r'(?u)\.\.\.(?![\s.,!?\'"])(?!$)'), "... ", name="CM_ellipsis_no_space"),
# multiple spaces
NReProcessor(re.compile(r'(?u)[\s]{2,}'), " ", name="CM_multiple_spaces"),
# no space after starting dash
NReProcessor(re.compile(r'(?u)^-(?![\s-])'), "- ", name="CM_dash_space"),
# '' = "
StringProcessor("''", '"', name="CM_double_apostrophe"),
# space missing before doublequote
#ReProcessor(re.compile(r'(?u)(?<!^)(?<![\s(\["])("[^"]+")'), r' \1', name="CM_space_before_dblquote"),
# space missing after doublequote
#ReProcessor(re.compile(r'(?u)("[^"\s][^"]+")([^\s.,!?)\]]+)'), r"\1 \2", name="CM_space_after_dblquote"),
# space before ending doublequote?
# -- = ...
StringProcessor("-- ", '... ', name="CM_doubledash"),
# remove >>
NReProcessor(re.compile(r'(?u)^>>[\s]*'), "", name="CM_leading_crocodiles"),
# remove leading ...
NReProcessor(re.compile(r'(?u)^\.\.\.[\s]*'), "", name="CM_leading_ellipsis"),
# replace uppercase I with lowercase L in words
NReProcessor(re.compile(r'(?u)([A-z]+)I([a-z]+)'), r"\1l\2", name="CM_uppercase_i_in_word"),
# fix spaces in numbers
NReProcessor(re.compile(r'(?u)([0-9]+)[\s]+([0-9:.]*[0-9]+)'), r"\1\2", name="CM_spaces_in_numbers"),
]
registry.register(CommonFixes)
@@ -0,0 +1,27 @@
# coding=utf-8
import logging
from subzero.modification.mods import SubtitleModification
from subzero.modification import registry
logger = logging.getLogger(__name__)
class ChangeFPS(SubtitleModification):
identifier = "change_FPS"
description = "Change the FPS of the subtitle"
exclusive = True
advanced = True
modifies_whole_file = True
long_description = """\
Re-syncs the subtitle to the framerate of the current media file.
"""
def modify(self, content, debug=False, parent=None, **kwargs):
fps_from = kwargs.get("from")
fps_to = kwargs.get("to")
parent.f.transform_framerate(float(fps_from), float(fps_to))
registry.register(ChangeFPS)
@@ -11,18 +11,22 @@ class HearingImpaired(SubtitleTextModification):
description = "Remove Hearing Impaired tags"
exclusive = True
long_description = """\
Removes tags, text and characters from subtitles that are meant for hearing impaired people
"""
processors = [
# brackets
NReProcessor(re.compile(r'(?sux)[([].+[)\]]'), "", name="HI_brackets"),
# brackets (only remove if at least 3 consecutive uppercase chars in brackets
NReProcessor(re.compile(r'(?sux)[([].+(?=[A-Z]{3,}).+[)\]]'), "", name="HI_brackets"),
# text before colon (and possible dash in front), max 11 chars after the first whitespace (if any)
NReProcessor(re.compile(r'(?u)(^[A-z\-\'"_]+[\w\s]{0,11}:[^0-9{2}][\s]*)'), "", name="HI_before_colon"),
# all caps line (at least 3 chars)
NReProcessor(re.compile(r'(?u)(^[A-Z]{3,}$)'), "", name="HI_all_caps"),
# all caps line (at least 4 chars)
NReProcessor(re.compile(r'(?u)(^(?=.*[A-Z]{4,})[A-Z\s]+$)'), "", name="HI_all_caps"),
# dash in front
NReProcessor(re.compile(r'(?u)^\s*-\s*'), "", name="HI_starting_dash"),
# NReProcessor(re.compile(r'(?u)^\s*-\s*'), "", name="HI_starting_dash"),
]
@@ -16,6 +16,10 @@ class FixOCR(SubtitleTextModification):
exclusive = True
data_dict = None
long_description = """\
Fix issues that happen when a subtitle gets converted from bitmap to text through OCR
"""
def __init__(self, parent):
super(FixOCR, self).__init__(parent)
data_dict = OCR_fix_data.get(parent.language.alpha3t)
@@ -0,0 +1,27 @@
# coding=utf-8
import logging
from subzero.modification.mods import SubtitleModification
from subzero.modification import registry
logger = logging.getLogger(__name__)
class ShiftOffset(SubtitleModification):
identifier = "shift_offset"
description = "Change the timing of the subtitle"
exclusive = True
advanced = True
modifies_whole_file = True
long_description = """\
Adds or substracts a certain amount of time from the whole subtitle to match your media
"""
def modify(self, content, debug=False, parent=None, **kwargs):
parent.f.shift(h=int(kwargs.get("h", 0)), m=int(kwargs.get("m", 0)), s=int(kwargs.get("s", 0)),
ms=int(kwargs.get("ms", 0)))
registry.register(ShiftOffset)
@@ -31,7 +31,8 @@ class NReProcessor(ReProcessor):
def process(self, content, debug=False):
lines = []
for line in content.split(r"\N"):
a = super(NReProcessor, self).process(line, debug=debug)
a = line.strip()
a = super(NReProcessor, self).process(a, debug=debug)
if not a:
continue
lines.append(a)
+15 -14
View File
@@ -4,25 +4,25 @@
2
00:00:10,759 --> 00:00:12,678
ROSE: So what is it?
What's wrong?
ROSE: (Help us. Please. . .help us.)
What's "wrong"?
3
00:00:12,679 --> 00:00:16,097
I don't know. Some kind
I don't know. Some kind of wrong "1 00" number
of signal, drawing the Tardis off course.
4
00:00:16,099 --> 00:00:17,224
this is a subtitle test with a text before colons: Where are we?
this is a"subtitle" test "with a"text before colons and "peter"following: Where are we?
5
00:00:17,225 --> 00:00:19,684
less text before colons: Earth. Utah, North America.
"less text before colons: Earth. Utah, North America."
6
00:00:19,686 --> 00:00:21,103
Ithinkyou're About half a mile underground.
Ithinkyou're About half a miIe underground.
7
00:00:21,103 --> 00:00:23,603
@@ -30,11 +30,11 @@ lrn gonna And when are we?
8
00:00:24,274 --> 00:00:26,649
2012.
...2012. it's 1 2:00 o'clock
9
00:00:26,650 --> 00:00:29,370
God, that's so close. I should be 26!
(BIG BROTHER THEME MUSIC)
10
00:00:30,612 --> 00:00:33,112
@@ -43,27 +43,28 @@ God, that's so close. I should be 26!
11
00:00:33,658 --> 00:00:34,783
(WHOO
SHING) geil
SHING) >>geil
12
00:00:34,783 --> 00:00:36,826
Blimey.
-- Blimey.
13
00:00:36,828 --> 00:00:39,328
ROSE: Like a great big museum.
ROSE: Like a "great...big museum".
14
00:00:40,414 --> 00:00:42,914
DOCTOR's MOM: An alien museum.
DOCTOR's MOM: ''An alien museum".
15
00:00:43,542 --> 00:00:46,042
Someone's got a hobby.
Someone's got a hobby.
16
00:00:46,378 --> 00:00:49,048
They must've spent a fortune on this.
FULL UPPERCASE LINE HERE
and some text
17
00:00:49,631 --> 00:00:51,924
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

+16
View File
@@ -10,6 +10,22 @@ Checkout **[the Sub-Zero Wiki](https://github.com/pannal/Sub-Zero.bundle/wiki)**
## Changelog
2.0.12.1180 RC1
- core: update subliminal to version 2
- core: update all dependencies
- core: add new providers: legendastv (pt-BR), napiprojekt (pl), shooter (cn), subscenter (heb)
- core: rewritten all subliminal patches for version 2
- menu: add icons for menu items; update main channel icon
- core: use SSL again for opensubtitles
- core: improved matching due to subliminal 2 (and SZ custom) tvdb/omdb refiners
- menu: add "Get my logs" function to the advanced menu, which zips up all necessary logs suitable for posting in the forums
- core: on non-windows systems, utilize a file-based cache database for provider media lists and subliminal refiner results
- core: add manual and automatic subtitle modification framework (fix common OCR issues, remove hearing impaired etc.)
- menu: add subtitle modifications (subtitle content fixes, offset-based shifting, framerate conversion)
- menu: add recently played menu
- improve almost everything Sub-Zero did in 1.4 :)
1.4.27.973
- core: ignore "obfuscated" and "scrambled" tags in filenames when searching for subtitles
- core: exotic embedded subtitles are now also considered when searching (and when the option is enabled); fixes #264