Files
Sub-Zero.bundle/Contents/Libraries/Shared/subzero/modification.py
T
2017-05-01 04:01:45 +02:00

247 lines
6.7 KiB
Python

# coding=utf-8
import re
import traceback
from collections import OrderedDict
import pysubs2
import logging
logger = logging.getLogger(__name__)
class SubtitleModifications(object):
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)
elif content:
self.f = pysubs2.SSAFile.from_string(content, fps=fps)
except (IOError,
UnicodeDecodeError,
pysubs2.exceptions.UnknownFPSError,
pysubs2.exceptions.UnknownFormatIdentifierError,
pysubs2.exceptions.FormatAutodetectionError):
if fn:
logger.exception("Couldn't load subtitle: %s: %s", fn, traceback.format_exc())
elif content:
logger.exception("Couldn't load subtitle: %s", traceback.format_exc())
def modify(self, *mods):
new_f = []
for line in self.f:
applied_mods = []
for identifier in mods:
if identifier in registry.mods:
mod = registry.mods[identifier]
# 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", encoding="utf-8"):
return self.f.to_string(format, encoding=encoding)
def save(self, fn):
self.f.save(fn)
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, name=None):
super(StringProcessor, self).__init__(name=name)
self.search = search
self.replace = replace
def process(self, content):
return content.replace(self.search, self.replace)
class ReProcessor(Processor):
"""
Regex processor
"""
pattern = None
replace_with = None
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, debug=False):
return self.pattern.sub(self.replace_with, content)
class NReProcessor(ReProcessor):
"""
Single line regex processor
"""
def process(self, content, debug=False):
lines = []
for line in content.split(r"\N"):
a = super(NReProcessor, self).process(line, debug=debug)
if not a:
continue
lines.append(a)
return r"\N".join(lines)
class SubtitleModification(object):
identifier = None
description = None
exclusive = False
pre_processors = []
processors = []
post_processors = []
@classmethod
def _process(cls, content, processors, debug=False):
if not content:
return
new_content = content
for processor in processors:
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, debug=False):
return cls._process(content, cls.pre_processors, debug=debug)
@classmethod
def process(cls, content, debug=False):
return cls._process(content, cls.processors, debug=debug)
@classmethod
def post_process(cls, content, debug=False):
return cls._process(content, cls.post_processors, debug=debug)
@classmethod
def modify(cls, content, debug=False):
new_content = content
for method in ("pre_process", "process", "post_process"):
new_content = getattr(cls, method)(new_content, debug=debug)
return new_content
class SubtitleTextModification(SubtitleModification):
post_processors = [
# empty tag
ReProcessor(re.compile(r'({\\\w+1})[\s.,-_!?]+({\\\w+0})'), "", name="empty_tag"),
# empty line (needed?)
NReProcessor(re.compile(r'^\s+$'), "", name="empty_line"),
# empty dash line (needed?)
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", name="surrounding_whitespace"),
]
class HearingImpaired(SubtitleTextModification):
identifier = "remove_HI"
description = "Remove Hearing Impaired tags"
exclusive = True
processors = [
# brackets
NReProcessor(re.compile(r'(?sux)[([].+[)\]]'), "", name="HI_brackets"),
# text before colon (and possible dash in front)
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'(?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)