Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b995f16c34 | |||
| 49c7adcc40 | |||
| 88eee6fe48 | |||
| cbe425d150 | |||
| 1c7d6b7bf8 | |||
| 8323608558 | |||
| 3f8a5ec125 | |||
| 464b1695a9 | |||
| d85602612b | |||
| 59440d251b | |||
| d774f09427 | |||
| 45be650db9 | |||
| d54847803f | |||
| ce3b66eda7 |
@@ -224,7 +224,7 @@ class SubZeroAgent(object):
|
||||
whack_missing_parts(scanned_video_part_map)
|
||||
|
||||
if downloaded_subtitles:
|
||||
save_subtitles(scanned_video_part_map, downloaded_subtitles)
|
||||
save_subtitles(scanned_video_part_map, downloaded_subtitles, mods=config.default_mods)
|
||||
track_usage("Subtitle", "refreshed", "download", 1)
|
||||
|
||||
for video, video_subtitles in downloaded_subtitles.items():
|
||||
|
||||
@@ -15,7 +15,7 @@ from support.helpers import timestamp, pad_title
|
||||
from support.config import config
|
||||
from support.lib import Plex
|
||||
from support.storage import reset_storage, log_storage
|
||||
from support.background import scheduler
|
||||
from support.scheduler import scheduler
|
||||
|
||||
|
||||
@route(PREFIX + '/advanced')
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
# coding=utf-8
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from babelfish import Language
|
||||
|
||||
from subzero.constants import PREFIX
|
||||
from menu_helpers import debounce, SubFolderObjectContainer, default_thumb, add_ignore_options, get_item_task_data, \
|
||||
set_refresh_menu_state
|
||||
from refresh_item import RefreshItem
|
||||
from support.helpers import timestamp, cast_bool, df, get_language
|
||||
from support.items import get_item_kind_from_rating_key, get_item
|
||||
from support.items import get_item_kind_from_rating_key, get_item, get_current_sub
|
||||
from support.plex_media import get_plex_metadata, scan_videos
|
||||
from support.lib import Plex
|
||||
from support.storage import get_subtitle_storage
|
||||
from support.storage import get_subtitle_storage, save_subtitles
|
||||
from support.config import config
|
||||
from support.background import scheduler
|
||||
from support.scheduler import scheduler
|
||||
|
||||
from subliminal_patch import PatchedSubtitle as Subtitle
|
||||
from subzero.modification import registry as mod_registry
|
||||
|
||||
|
||||
@route(PREFIX + '/item/{rating_key}/actions')
|
||||
@@ -89,12 +95,12 @@ def ItemDetailsMenu(rating_key, title=None, base_title=None, item_title=None, ra
|
||||
current_sub.score, current_sub.storage_type)
|
||||
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(ListAvailableSubsForItemMenu, rating_key=rating_key, part_id=part_id, title=title,
|
||||
item_title=item_title, language=lang, current_id=current_sub_id,
|
||||
key=Callback(SubtitleOptionsMenu, rating_key=rating_key, part_id=part_id, title=title,
|
||||
item_title=item_title, language=lang, language_name=lang.name, current_id=current_sub_id,
|
||||
item_type=plex_item.type, filename=filename, current_data=summary,
|
||||
randomize=timestamp(), current_provider=current_sub_provider_name,
|
||||
current_score=current_score),
|
||||
title=u"List %s subtitles" % lang.name,
|
||||
title=u"Actions for %s subtitle" % lang.name,
|
||||
summary=summary
|
||||
))
|
||||
|
||||
@@ -103,10 +109,106 @@ def ItemDetailsMenu(rating_key, title=None, base_title=None, item_title=None, ra
|
||||
return oc
|
||||
|
||||
|
||||
@route(PREFIX + '/item/current_sub/{rating_key}/{part_id}', force=bool)
|
||||
@debounce
|
||||
def SubtitleOptionsMenu(**kwargs):
|
||||
oc = SubFolderObjectContainer(title2=kwargs["title"], replace_parent=True)
|
||||
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.add(DirectoryObject(
|
||||
key=Callback(ItemDetailsMenu, rating_key=kwargs["rating_key"], item_title=kwargs["item_title"],
|
||||
title=kwargs["title"], randomize=timestamp()),
|
||||
title=u"Back to: %s" % kwargs["title"],
|
||||
summary=kwargs["current_data"],
|
||||
thumb=default_thumb
|
||||
))
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(ListAvailableSubsForItemMenu, randomize=timestamp(), **kwargs),
|
||||
title=u"List %s subtitles" % kwargs["language_name"],
|
||||
summary=kwargs["current_data"]
|
||||
))
|
||||
if current_sub:
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SubtitleModificationsMenu, randomize=timestamp(), **kwargs),
|
||||
title=u"Modify %s subtitle" % kwargs["language_name"],
|
||||
summary=u"Currently applied mods: %s" % (", ".join(current_sub.mods) if current_sub.mods else "none")
|
||||
))
|
||||
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,
|
||||
item_type="episode", language=None, force=False, current_id=None, current_data=None,
|
||||
item_type="episode", language=None, language_name=None, force=False, current_id=None,
|
||||
current_data=None,
|
||||
current_provider=None, current_score=None, randomize=None):
|
||||
assert rating_key, part_id
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from subzero.constants import PREFIX, TITLE, ART
|
||||
from support.config import config
|
||||
from support.helpers import pad_title, timestamp, df
|
||||
from support.background import scheduler
|
||||
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 menu_helpers import main_icon, debounce, SubFolderObjectContainer, default_thumb, dig_tree, add_ignore_options
|
||||
|
||||
@@ -9,7 +9,7 @@ from menu_helpers import add_ignore_options, dig_tree, set_refresh_menu_state, \
|
||||
from main import fatality, IgnoreMenu
|
||||
from advanced import DispatchRestart
|
||||
from subzero.constants import ART, PREFIX, DEPENDENCY_MODULE_NAMES
|
||||
from support.background import scheduler
|
||||
from support.scheduler import scheduler
|
||||
from support.config import config
|
||||
from support.helpers import timestamp, df
|
||||
from support.ignore import ignore_list
|
||||
|
||||
@@ -8,7 +8,7 @@ from support.ignore import ignore_list
|
||||
from support.lib import get_intent
|
||||
from support.config import config
|
||||
from subzero.constants import ICON_SUB, ICON
|
||||
from support.background import scheduler
|
||||
from support.scheduler import scheduler
|
||||
|
||||
default_thumb = R(ICON_SUB)
|
||||
main_icon = ICON if not config.is_development else "icon-dev.jpg"
|
||||
|
||||
@@ -32,9 +32,9 @@ import missing_subtitles
|
||||
|
||||
sys.modules["support.missing_subtitles"] = missing_subtitles
|
||||
|
||||
import background
|
||||
import scheduler
|
||||
|
||||
sys.modules["support.background"] = background
|
||||
sys.modules["support.scheduler"] = scheduler
|
||||
|
||||
import tasks
|
||||
|
||||
|
||||
@@ -67,12 +67,14 @@ class Config(object):
|
||||
notify_executable = None
|
||||
sections = None
|
||||
enabled_sections = None
|
||||
remove_hi = False
|
||||
enforce_encoding = False
|
||||
chmod = None
|
||||
forced_only = False
|
||||
exotic_ext = False
|
||||
treat_und_as_first = False
|
||||
ext_match_strictness = False
|
||||
default_mods = None
|
||||
use_activities = False
|
||||
activity_mode = None
|
||||
|
||||
@@ -106,12 +108,14 @@ class Config(object):
|
||||
self.enabled_sections = self.check_enabled_sections()
|
||||
self.permissions_ok = self.check_permissions()
|
||||
self.notify_executable = self.check_notify_executable()
|
||||
self.remove_hi = cast_bool(Prefs['subtitles.remove_hi'])
|
||||
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()
|
||||
self.default_mods = self.get_default_mods()
|
||||
self.initialized = True
|
||||
|
||||
def set_log_paths(self):
|
||||
@@ -404,6 +408,13 @@ class Config(object):
|
||||
return "loose"
|
||||
return "strict"
|
||||
|
||||
def get_default_mods(self):
|
||||
mods = []
|
||||
if self.remove_hi:
|
||||
mods.append("remove_HI")
|
||||
|
||||
return mods
|
||||
|
||||
def set_activity_modes(self):
|
||||
val = Prefs["activity.on_playback"]
|
||||
if val == "never":
|
||||
|
||||
@@ -283,3 +283,13 @@ def refresh_item(rating_key, force=False, timeout=8000, refresh_kind=None, paren
|
||||
for key in refresh:
|
||||
Log.Info("%s item %s", "Refreshing" if not force else "Forced-refreshing", key)
|
||||
Plex["library/metadata"].refresh(key)
|
||||
|
||||
|
||||
def get_current_sub(rating_key, part_id, language):
|
||||
from support.storage import get_subtitle_storage
|
||||
|
||||
item = get_item(rating_key)
|
||||
subtitle_storage = get_subtitle_storage()
|
||||
stored_subs = subtitle_storage.load_or_new(item)
|
||||
current_sub = stored_subs.get_any(part_id, language)
|
||||
return current_sub, stored_subs, subtitle_storage
|
||||
Executable → Regular
@@ -6,16 +6,17 @@ import pprint
|
||||
import copy
|
||||
|
||||
import subliminal
|
||||
from items import get_item
|
||||
from subzero.subtitle_storage import StoredSubtitlesManager
|
||||
|
||||
from subtitlehelpers import force_utf8
|
||||
from config import config
|
||||
from helpers import notify_executable, get_title_for_video_metadata, cast_bool, force_unicode
|
||||
from plex_media import PMSMediaProxy
|
||||
from support.items import get_item
|
||||
|
||||
|
||||
get_subtitle_storage = lambda: StoredSubtitlesManager(Data, get_item)
|
||||
def get_subtitle_storage():
|
||||
return StoredSubtitlesManager(Data, get_item)
|
||||
|
||||
|
||||
def whack_missing_parts(scanned_video_part_map, existing_parts=None):
|
||||
@@ -156,9 +157,29 @@ def save_subtitles_to_metadata(videos, subtitles):
|
||||
return True
|
||||
|
||||
|
||||
def save_subtitles(scanned_video_part_map, downloaded_subtitles, mode="a"):
|
||||
def save_subtitles(scanned_video_part_map, downloaded_subtitles, mode="a", bare_save=False, mods=None):
|
||||
"""
|
||||
|
||||
:param scanned_video_part_map:
|
||||
:param downloaded_subtitles:
|
||||
:param mode:
|
||||
:param bare_save: don't trigger anything; don't store information
|
||||
:param mods: enabled mods
|
||||
:return:
|
||||
"""
|
||||
meta_fallback = False
|
||||
save_successful = False
|
||||
|
||||
if mods:
|
||||
for video, video_subtitles in downloaded_subtitles.items():
|
||||
if not video_subtitles:
|
||||
continue
|
||||
|
||||
for subtitle in video_subtitles:
|
||||
Log.Info("Applying mods: %s to %s", mods, subtitle)
|
||||
subtitle.mods = mods
|
||||
subtitle.plex_media_fps = video.fps
|
||||
|
||||
storage = "metadata"
|
||||
if Prefs['subtitles.save.filesystem']:
|
||||
storage = "filesystem"
|
||||
@@ -180,8 +201,9 @@ def save_subtitles(scanned_video_part_map, downloaded_subtitles, mode="a"):
|
||||
Log.Debug("Using metadata as subtitle storage")
|
||||
save_successful = save_subtitles_to_metadata(scanned_video_part_map, downloaded_subtitles)
|
||||
|
||||
if save_successful and config.notify_executable:
|
||||
if not bare_save and save_successful and config.notify_executable:
|
||||
notify_executable(config.notify_executable, scanned_video_part_map, downloaded_subtitles, storage)
|
||||
|
||||
store_subtitle_info(scanned_video_part_map, downloaded_subtitles, storage, mode=mode)
|
||||
if not bare_save:
|
||||
store_subtitle_info(scanned_video_part_map, downloaded_subtitles, storage, mode=mode)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from subliminal import list_subtitles as list_all_subtitles
|
||||
from babelfish import Language
|
||||
|
||||
from missing_subtitles import items_get_all_missing_subs, refresh_item
|
||||
from background import scheduler
|
||||
from scheduler import scheduler
|
||||
from storage import save_subtitles, whack_missing_parts, get_subtitle_storage
|
||||
from support.config import config
|
||||
from support.items import get_recent_items, is_ignored, get_item
|
||||
|
||||
@@ -395,6 +395,12 @@
|
||||
],
|
||||
"default": "don't prefer"
|
||||
},
|
||||
{
|
||||
"id": "subtitles.remove_hi",
|
||||
"label": "Remove Hearing Impaired tags from downloaded subtitles",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"id": "subtitles.enforce_encoding",
|
||||
"label": "Normalize subtitle encoding to UTF-8",
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.0.0.9</string>
|
||||
<string>2.0.0.10</string>
|
||||
<key>PlexFrameworkVersion</key>
|
||||
<string>2</string>
|
||||
<key>PlexPluginClass</key>
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<h1>Sub-Zero for Plex</h1><i>Subtitles done right</i>
|
||||
|
||||
Version 2.0.0.9 DEV
|
||||
Version 2.0.0.10 DEV
|
||||
|
||||
Originally based on @bramwalet's awesome <a href="https://github.com/bramwalet/Subliminal.bundle">Subliminal.bundle</a>
|
||||
|
||||
|
||||
@@ -515,7 +515,7 @@ def save_subtitles(video, subtitles, single=False, directory=None, encoding=None
|
||||
|
||||
# save normalized subtitle if encoder or no encoding is given
|
||||
if has_encoder or encoding is None:
|
||||
content = encode_with(subtitle.text) if has_encoder else subtitle.content
|
||||
content = encode_with(subtitle.get_modified_text()) if has_encoder else subtitle.get_modified_content()
|
||||
with io.open(subtitle_path, 'wb') as f:
|
||||
f.write(content)
|
||||
|
||||
@@ -530,7 +530,7 @@ def save_subtitles(video, subtitles, single=False, directory=None, encoding=None
|
||||
# save subtitle if encoding given
|
||||
if encoding is not None:
|
||||
with io.open(subtitle_path, 'w', encoding=encoding) as f:
|
||||
f.write(subtitle.text)
|
||||
f.write(subtitle.get_modified_text())
|
||||
|
||||
# change chmod if requested
|
||||
if chmod:
|
||||
|
||||
@@ -74,7 +74,7 @@ class OpenSubtitlesProvider(ProviderRetryMixin, _OpenSubtitlesProvider):
|
||||
logger.info("Only searching for foreign/forced subtitles")
|
||||
|
||||
super(OpenSubtitlesProvider, self).__init__()
|
||||
self.server = ServerProxy('https://api.opensubtitles.org/xml-rpc', TimeoutSafeTransport(10))
|
||||
self.server = ServerProxy('https://api.opensubtitles.org/xml-rpc', TimeoutSafeTransport(4))
|
||||
|
||||
def initialize(self):
|
||||
logger.info('Logging in')
|
||||
|
||||
@@ -8,6 +8,7 @@ import pysrt
|
||||
import pysubs2
|
||||
from bs4 import UnicodeDammit
|
||||
from subliminal import Subtitle
|
||||
from subzero.modification import SubtitleModifications
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -17,6 +18,13 @@ class PatchedSubtitle(Subtitle):
|
||||
release_info = None
|
||||
matches = None
|
||||
hash_verifiable = False
|
||||
mods = None
|
||||
plex_media_fps = None
|
||||
|
||||
def __init__(self, language, hearing_impaired=False, page_link=None, encoding=None, mods=None):
|
||||
super(PatchedSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, page_link=page_link,
|
||||
encoding=encoding)
|
||||
self.mods = mods
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r [%s]>' % (
|
||||
@@ -126,4 +134,30 @@ class PatchedSubtitle(Subtitle):
|
||||
logger.exception("Couldn't convert subtitle %s to .srt format", self)
|
||||
return False
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
def get_modified_content(self):
|
||||
"""
|
||||
:param language:
|
||||
:param fps:
|
||||
:return: string
|
||||
"""
|
||||
if not self.mods:
|
||||
return self.content
|
||||
|
||||
encoding = self.guess_encoding()
|
||||
|
||||
submods = SubtitleModifications()
|
||||
submods.load(content=self.text, fps=self.plex_media_fps)
|
||||
submods.modify(*self.mods)
|
||||
return submods.to_string("srt", encoding=encoding).encode(encoding=encoding)
|
||||
|
||||
def get_modified_text(self):
|
||||
"""
|
||||
:param language:
|
||||
:param fps:
|
||||
:return: unicode
|
||||
"""
|
||||
content = self.get_modified_content()
|
||||
encoding = self.guess_encoding()
|
||||
return content.decode(encoding=encoding)
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
# coding=utf-8
|
||||
|
||||
from subzero.modification import SubMod, HearingImpaired
|
||||
import sys
|
||||
import logging
|
||||
|
||||
submod = SubMod("test.srt")
|
||||
submod.modify(HearingImpaired)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
print submod.f.to_string("srt")
|
||||
from subzero.modification import SubMod
|
||||
|
||||
fn = sys.argv[1]
|
||||
debug = "--debug" in sys.argv
|
||||
|
||||
if debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
submod = SubMod(debug=debug)
|
||||
submod.load(fn)
|
||||
submod.modify("remove_HI")
|
||||
|
||||
#print submod.f.to_string("srt")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import re
|
||||
import traceback
|
||||
from collections import OrderedDict
|
||||
|
||||
import pysubs2
|
||||
import logging
|
||||
@@ -10,7 +11,19 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SubtitleModifications(object):
|
||||
def __init__(self, fn=None, content=None, fps=None):
|
||||
debug = False
|
||||
|
||||
def __init__(self, debug=False):
|
||||
self.debug = debug
|
||||
|
||||
def load(self, fn=None, content=None, fps=None):
|
||||
"""
|
||||
|
||||
:param fn: filename
|
||||
:param content: unicode
|
||||
:param fps:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
if fn:
|
||||
self.f = pysubs2.load(fn, fps=fps)
|
||||
@@ -29,19 +42,29 @@ class SubtitleModifications(object):
|
||||
def modify(self, *mods):
|
||||
new_f = []
|
||||
for line in self.f:
|
||||
for mod in mods:
|
||||
new_content = mod.modify(line.text)
|
||||
if not new_content:
|
||||
logger.debug("deleting %s", line)
|
||||
continue
|
||||
applied_mods = []
|
||||
for identifier in mods:
|
||||
if identifier in registry.mods:
|
||||
mod = registry.mods[identifier]
|
||||
|
||||
line.text = new_content
|
||||
new_f.append(line)
|
||||
# don't bother reapplying exclusive mods multiple times
|
||||
if mod.exclusive and identifier in applied_mods:
|
||||
continue
|
||||
|
||||
new_content = mod.modify(line.text, debug=self.debug)
|
||||
if not new_content:
|
||||
if self.debug:
|
||||
logger.debug("%s: deleting %s", identifier, line)
|
||||
continue
|
||||
|
||||
line.text = new_content
|
||||
new_f.append(line)
|
||||
applied_mods.append(identifier)
|
||||
|
||||
self.f.events = new_f
|
||||
|
||||
def to_string(self, format="srt"):
|
||||
return self.f.to_string(format)
|
||||
def to_string(self, format="srt", encoding="utf-8"):
|
||||
return self.f.to_string(format, encoding=encoding)
|
||||
|
||||
def save(self, fn):
|
||||
self.f.save(fn)
|
||||
@@ -50,21 +73,54 @@ class SubtitleModifications(object):
|
||||
SubMod = SubtitleModifications
|
||||
|
||||
|
||||
class SubtitleModRegistry(object):
|
||||
mods = None
|
||||
mods_available = None
|
||||
|
||||
def __init__(self):
|
||||
self.mods = OrderedDict()
|
||||
self.mods_available = []
|
||||
|
||||
def register(self, mod):
|
||||
self.mods[mod.identifier] = mod
|
||||
self.mods_available.append(mod.identifier)
|
||||
|
||||
registry = SubtitleModRegistry()
|
||||
|
||||
|
||||
class Processor(object):
|
||||
"""
|
||||
Processor base class
|
||||
"""
|
||||
name = None
|
||||
|
||||
def __init__(self, name=None):
|
||||
self.name = name
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
return self.name
|
||||
|
||||
def process(self, content):
|
||||
return content
|
||||
|
||||
def __repr__(self):
|
||||
return "Processor <%s %s>" % (self.__class__.__name__, self.info)
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(repr(self))
|
||||
|
||||
|
||||
class StringProcessor(Processor):
|
||||
"""
|
||||
String replacement processor base
|
||||
"""
|
||||
|
||||
def __init__(self, search, replace):
|
||||
def __init__(self, search, replace, name=None):
|
||||
super(StringProcessor, self).__init__(name=name)
|
||||
self.search = search
|
||||
self.replace = replace
|
||||
|
||||
@@ -79,11 +135,12 @@ class ReProcessor(Processor):
|
||||
pattern = None
|
||||
replace_with = None
|
||||
|
||||
def __init__(self, pattern, replace_with):
|
||||
def __init__(self, pattern, replace_with, name=None):
|
||||
super(ReProcessor, self).__init__(name=name)
|
||||
self.pattern = pattern
|
||||
self.replace_with = replace_with
|
||||
|
||||
def process(self, content):
|
||||
def process(self, content, debug=False):
|
||||
return self.pattern.sub(self.replace_with, content)
|
||||
|
||||
|
||||
@@ -92,10 +149,10 @@ class NReProcessor(ReProcessor):
|
||||
Single line regex processor
|
||||
"""
|
||||
|
||||
def process(self, content):
|
||||
def process(self, content, debug=False):
|
||||
lines = []
|
||||
for line in content.split(r"\N"):
|
||||
a = super(NReProcessor, self).process(line)
|
||||
a = super(NReProcessor, self).process(line, debug=debug)
|
||||
if not a:
|
||||
continue
|
||||
lines.append(a)
|
||||
@@ -103,37 +160,49 @@ class NReProcessor(ReProcessor):
|
||||
|
||||
|
||||
class SubtitleModification(object):
|
||||
identifier = None
|
||||
description = None
|
||||
exclusive = False
|
||||
pre_processors = []
|
||||
processors = []
|
||||
post_processors = []
|
||||
|
||||
@classmethod
|
||||
def _process(cls, content, processors):
|
||||
def _process(cls, content, processors, debug=False):
|
||||
if not content:
|
||||
return
|
||||
|
||||
new_content = content
|
||||
for processor in processors:
|
||||
new_content = processor.process(new_content)
|
||||
old_content = new_content
|
||||
new_content = processor.process(new_content, debug=debug)
|
||||
if not new_content:
|
||||
if debug:
|
||||
logger.debug("Processor returned empty line: %s", processor)
|
||||
break
|
||||
if debug:
|
||||
if old_content == new_content:
|
||||
continue
|
||||
logger.debug("%s: %s -> %s", processor, old_content, new_content)
|
||||
return new_content
|
||||
|
||||
@classmethod
|
||||
def pre_process(cls, content):
|
||||
return cls._process(content, cls.pre_processors)
|
||||
def pre_process(cls, content, debug=False):
|
||||
return cls._process(content, cls.pre_processors, debug=debug)
|
||||
|
||||
@classmethod
|
||||
def process(cls, content):
|
||||
return cls._process(content, cls.processors)
|
||||
def process(cls, content, debug=False):
|
||||
return cls._process(content, cls.processors, debug=debug)
|
||||
|
||||
@classmethod
|
||||
def post_process(cls, content):
|
||||
return cls._process(content, cls.post_processors)
|
||||
def post_process(cls, content, debug=False):
|
||||
return cls._process(content, cls.post_processors, debug=debug)
|
||||
|
||||
@classmethod
|
||||
def modify(cls, content):
|
||||
def modify(cls, content, debug=False):
|
||||
new_content = content
|
||||
for method in ("pre_process", "process", "post_process"):
|
||||
new_content = getattr(cls, method)(new_content)
|
||||
new_content = getattr(cls, method)(new_content, debug=debug)
|
||||
|
||||
return new_content
|
||||
|
||||
@@ -141,27 +210,37 @@ class SubtitleModification(object):
|
||||
class SubtitleTextModification(SubtitleModification):
|
||||
post_processors = [
|
||||
# empty tag
|
||||
ReProcessor(re.compile(r'(<[A-z]+[^>]*>)[\s.,-_!?]+(</[A-z]>)'), ""),
|
||||
ReProcessor(re.compile(r'({\\\w+1})[\s.,-_!?]+({\\\w+0})'), "", name="empty_tag"),
|
||||
|
||||
# empty line (needed?)
|
||||
ReProcessor(re.compile(r'(?m)^\s+$'), ""),
|
||||
NReProcessor(re.compile(r'^\s+$'), "", name="empty_line"),
|
||||
|
||||
# empty dash line (needed?)
|
||||
ReProcessor(re.compile(r'(?m)(^[\s]*[\-]+[\s]*)$'), ""),
|
||||
NReProcessor(re.compile(r'(^[\s]*[\-]+[\s]*)$'), "", name="empty_dash_line"),
|
||||
|
||||
# clean whitespace at start and end
|
||||
ReProcessor(re.compile(r'^\s*([^\s]+)\s*$'), r"\1"),
|
||||
ReProcessor(re.compile(r'^\s*([^\s]+)\s*$'), r"\1", name="surrounding_whitespace"),
|
||||
]
|
||||
|
||||
|
||||
class HearingImpaired(SubtitleTextModification):
|
||||
identifier = "remove_HI"
|
||||
description = "Remove Hearing Impaired tags"
|
||||
exclusive = True
|
||||
|
||||
processors = [
|
||||
# brackets
|
||||
ReProcessor(re.compile(r'(?sux)[([{].+[)\]}]'), ""),
|
||||
NReProcessor(re.compile(r'(?sux)[([].+[)\]]'), "", name="HI_brackets"),
|
||||
|
||||
# text before colon (and possible dash in front)
|
||||
ReProcessor(re.compile(r'(?mu)(^[A-z\-]+[\w\s]*:[^0-9{2}][\s]*)'), ""),
|
||||
NReProcessor(re.compile(r'(?u)(^[A-z\-]+[\w\s]*:[^0-9{2}][\s]*)'), "", name="HI_before_colon"),
|
||||
|
||||
# all caps line (at least 3 chars
|
||||
NReProcessor(re.compile(r'(?mu)(^[A-Z\.\-_]{3,}$)'), ""),
|
||||
# all caps line (at least 3 chars)
|
||||
NReProcessor(re.compile(r'(?u)(^[A-Z]{3,}$)'), "", name="HI_all_caps"),
|
||||
|
||||
# dash in front
|
||||
NReProcessor(re.compile(r'(?u)^\s*-\s*'), "", name="HI_starting_dash"),
|
||||
]
|
||||
|
||||
|
||||
registry.register(HearingImpaired)
|
||||
|
||||
@@ -6,7 +6,6 @@ import logging
|
||||
import traceback
|
||||
|
||||
from constants import mode_map
|
||||
from modification import SubtitleModifications
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -22,7 +21,8 @@ class StoredSubtitle(object):
|
||||
content = None
|
||||
mods = None
|
||||
|
||||
def __init__(self, score, storage_type, hash, provider_name, id, date_added=None, mode="a", content=None):
|
||||
def __init__(self, score, storage_type, hash, provider_name, id, date_added=None, mode="a", content=None,
|
||||
mods=None):
|
||||
self.score = int(score)
|
||||
self.storage_type = storage_type
|
||||
self.hash = hash
|
||||
@@ -31,19 +31,20 @@ class StoredSubtitle(object):
|
||||
self.date_added = date_added or datetime.datetime.now()
|
||||
self.mode = mode
|
||||
self.content = content
|
||||
self.mods = []
|
||||
self.mods = mods or []
|
||||
|
||||
def add_mod(self, identifier):
|
||||
self.mods = self.mods or []
|
||||
if identifier is None:
|
||||
self.mods = []
|
||||
return
|
||||
|
||||
self.mods.append(identifier)
|
||||
|
||||
@property
|
||||
def mode_verbose(self):
|
||||
return mode_map.get(self.mode, "Unknown")
|
||||
|
||||
def get_modified_content(self, fps=None):
|
||||
if not self.mods:
|
||||
return self.content
|
||||
submods = SubtitleModifications(content=self.content, fps=fps)
|
||||
submods.modify(*self.mods)
|
||||
return submods.to_string("srt")
|
||||
|
||||
|
||||
class StoredVideoSubtitles(object):
|
||||
"""
|
||||
@@ -80,7 +81,7 @@ class StoredVideoSubtitles(object):
|
||||
sub_key = self.get_sub_key(subtitle.provider_name, subtitle.id)
|
||||
subs[sub_key] = StoredSubtitle(subtitle.score, storage_type, hashlib.md5(subtitle.content).hexdigest(),
|
||||
subtitle.provider_name, subtitle.id, date_added=date_added, mode=mode,
|
||||
content=subtitle.content)
|
||||
content=subtitle.content, mods=subtitle.mods)
|
||||
subs["current"] = sub_key
|
||||
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user