Trying to fix Segmentation fault caused by mediainfo in docker container. #2098
parent
7136383098
commit
7455496c4c
@ -0,0 +1,8 @@
|
|||||||
|
__title__ = 'trakit'
|
||||||
|
__version__ = '0.2.1'
|
||||||
|
__short_version__ = '0.2'
|
||||||
|
__author__ = 'RatoAQ'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
__url__ = 'https://github.com/ratoaq2/trakit'
|
||||||
|
|
||||||
|
from .api import TrakItApi, trakit
|
@ -0,0 +1,108 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import babelfish
|
||||||
|
|
||||||
|
from trakit import TrakItApi, __version__
|
||||||
|
|
||||||
|
logging.basicConfig(stream=sys.stdout, format='%(message)s')
|
||||||
|
logging.getLogger('CONSOLE').setLevel(logging.INFO)
|
||||||
|
logging.getLogger('trakit').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
console = logging.getLogger('CONSOLE')
|
||||||
|
logger = logging.getLogger('trakit')
|
||||||
|
|
||||||
|
|
||||||
|
def build_argument_parser() -> argparse.ArgumentParser:
|
||||||
|
"""Build the argument parser."""
|
||||||
|
opts = argparse.ArgumentParser()
|
||||||
|
opts.add_argument(
|
||||||
|
dest='value',
|
||||||
|
help='track title to guess',
|
||||||
|
type=str,
|
||||||
|
)
|
||||||
|
|
||||||
|
conf_opts = opts.add_argument_group('Configuration')
|
||||||
|
conf_opts.add_argument(
|
||||||
|
'-l',
|
||||||
|
'--expected-language',
|
||||||
|
dest='expected_language',
|
||||||
|
help='The expected language to be guessed',
|
||||||
|
type=str,
|
||||||
|
)
|
||||||
|
|
||||||
|
output_opts = opts.add_argument_group('Output')
|
||||||
|
output_opts.add_argument(
|
||||||
|
'--debug',
|
||||||
|
action='store_true',
|
||||||
|
dest='debug',
|
||||||
|
help='Print information for debugging trakit and for reporting bugs.'
|
||||||
|
)
|
||||||
|
output_opts.add_argument(
|
||||||
|
'-y',
|
||||||
|
'--yaml',
|
||||||
|
action='store_true',
|
||||||
|
dest='yaml',
|
||||||
|
help='Display output in yaml format'
|
||||||
|
)
|
||||||
|
|
||||||
|
information_opts = opts.add_argument_group('Information')
|
||||||
|
information_opts.add_argument('--version', action='version', version=__version__)
|
||||||
|
|
||||||
|
return opts
|
||||||
|
|
||||||
|
|
||||||
|
def _as_yaml(value: str, info: typing.Mapping[str, typing.Any]) -> str:
|
||||||
|
"""Convert info to string using YAML format."""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
def default_representer(r: yaml.representer.SafeRepresenter, data: typing.Any):
|
||||||
|
return r.represent_scalar('tag:yaml.org,2002:str', str(data))
|
||||||
|
|
||||||
|
yaml.representer.SafeRepresenter.add_representer(babelfish.Language, default_representer)
|
||||||
|
|
||||||
|
return yaml.safe_dump({value: dict(info)}, allow_unicode=True, sort_keys=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _as_json(info: typing.Mapping[str, typing.Any]) -> str:
|
||||||
|
"""Convert info to string using JSON format."""
|
||||||
|
return json.dumps(info, ensure_ascii=False, indent=2, default=str)
|
||||||
|
|
||||||
|
|
||||||
|
def dump(value: str, info: typing.Mapping[str, typing.Any], opts: argparse.Namespace) -> str:
|
||||||
|
"""Convert info to string using json or yaml format."""
|
||||||
|
if opts.yaml:
|
||||||
|
return _as_yaml(value, info)
|
||||||
|
|
||||||
|
return _as_json(info)
|
||||||
|
|
||||||
|
|
||||||
|
def trakit(value: str, opts: argparse.Namespace) -> typing.Mapping:
|
||||||
|
"""Extract video metadata."""
|
||||||
|
if not opts.yaml:
|
||||||
|
console.info('Parsing: %s', value)
|
||||||
|
options = {k: v for k, v in vars(opts).items() if v is not None}
|
||||||
|
info = TrakItApi().trakit(value, options)
|
||||||
|
console.info('TrakIt %s found: ', __version__)
|
||||||
|
console.info(dump(value, info, opts))
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: typing.Optional[typing.List[str]] = None):
|
||||||
|
"""Execute main function for entry point."""
|
||||||
|
argument_parser = build_argument_parser()
|
||||||
|
args = args or sys.argv[1:]
|
||||||
|
opts = argument_parser.parse_args(args)
|
||||||
|
|
||||||
|
if opts.debug:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
logging.getLogger('rebulk').setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
return trakit(opts.value, opts)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
@ -0,0 +1,24 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
from trakit.config import Config
|
||||||
|
from trakit.context import Context
|
||||||
|
from trakit.patterns import configure
|
||||||
|
|
||||||
|
|
||||||
|
class TrakItApi:
|
||||||
|
|
||||||
|
def __init__(self, config: typing.Optional[typing.Mapping[str, typing.Any]] = None):
|
||||||
|
self.rebulk = configure(Config(config))
|
||||||
|
|
||||||
|
def trakit(self, string: str, options: typing.Optional[typing.Mapping[str, typing.Any]] = None):
|
||||||
|
"""Return a mapping of extracted information."""
|
||||||
|
matches = self.rebulk.matches(string, Context(options))
|
||||||
|
guess: typing.Mapping[str, typing.Any] = matches.to_dict()
|
||||||
|
return guess
|
||||||
|
|
||||||
|
|
||||||
|
default_api = TrakItApi()
|
||||||
|
|
||||||
|
|
||||||
|
def trakit(string: str, options: typing.Optional[typing.Mapping[str, typing.Any]] = None):
|
||||||
|
return default_api.trakit(string, options)
|
@ -0,0 +1,19 @@
|
|||||||
|
import json
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from pkg_resources import resource_stream
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
def __init__(self, config: typing.Optional[typing.Mapping[str, typing.Any]]):
|
||||||
|
with resource_stream('trakit', 'data/config.json') as f:
|
||||||
|
cfg: typing.Dict[str, typing.Any] = json.load(f)
|
||||||
|
if config:
|
||||||
|
cfg.update(config)
|
||||||
|
|
||||||
|
self.ignored: typing.Set[str] = set(cfg.get('ignored', []))
|
||||||
|
self.countries: typing.Mapping[str, str] = cfg.get('countries', {})
|
||||||
|
self.languages: typing.Mapping[str, str] = cfg.get('languages', {})
|
||||||
|
self.scripts: typing.Mapping[str, str] = cfg.get('scripts', {})
|
||||||
|
self.regions: typing.Mapping[str, str] = cfg.get('regions', {})
|
||||||
|
self.implicit_languages: typing.Mapping[str, str] = cfg.get('implicit-languages', {})
|
@ -0,0 +1,22 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
import babelfish
|
||||||
|
|
||||||
|
|
||||||
|
class Context(dict):
|
||||||
|
def __init__(self, options: typing.Optional[typing.Mapping[str, typing.Any]] = None):
|
||||||
|
super().__init__(options or {})
|
||||||
|
language = self['expected_language'] if 'expected_language' in self else None
|
||||||
|
if language and not isinstance(language, babelfish.Language):
|
||||||
|
language = babelfish.Language.fromietf(str(language))
|
||||||
|
self.expected_language: typing.Optional[babelfish.Language] = language
|
||||||
|
|
||||||
|
def accept(self, lang: babelfish.Language):
|
||||||
|
if self.expected_language is None:
|
||||||
|
return True
|
||||||
|
if self.expected_language.alpha3 != lang.alpha3:
|
||||||
|
return False
|
||||||
|
if self.expected_language.script and self.expected_language != lang.script:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return not self.expected_language.country or self.expected_language == lang.country
|
@ -0,0 +1,32 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
from babelfish import Country, CountryReverseConverter, CountryReverseError
|
||||||
|
from babelfish.converters import CaseInsensitiveDict
|
||||||
|
|
||||||
|
|
||||||
|
class GuessCountryConverter(CountryReverseConverter):
|
||||||
|
def __init__(self, config: typing.Mapping[str, str]):
|
||||||
|
self.synonyms = CaseInsensitiveDict(config)
|
||||||
|
|
||||||
|
def convert(self, alpha2):
|
||||||
|
return str(Country(alpha2))
|
||||||
|
|
||||||
|
def reverse(self, name: str):
|
||||||
|
try:
|
||||||
|
return self.synonyms[name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if name.isupper() and len(name) == 2:
|
||||||
|
try:
|
||||||
|
return Country(name).alpha2
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for conv in (Country.fromname,):
|
||||||
|
try:
|
||||||
|
return conv(name).alpha2
|
||||||
|
except CountryReverseError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise CountryReverseError(name)
|
@ -0,0 +1,30 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
from babelfish import Language, LanguageReverseConverter, LanguageReverseError
|
||||||
|
from babelfish.converters import CaseInsensitiveDict
|
||||||
|
|
||||||
|
|
||||||
|
class GuessLanguageConverter(LanguageReverseConverter):
|
||||||
|
def __init__(self, config: typing.Mapping[str, str]):
|
||||||
|
self.synonyms = CaseInsensitiveDict()
|
||||||
|
for synonym, code in config.items():
|
||||||
|
lang = Language.fromietf(code) if '-' in code else Language(code)
|
||||||
|
self.synonyms[synonym] = (lang.alpha3, lang.country.alpha2 if lang.country else None, lang.script)
|
||||||
|
|
||||||
|
def convert(self, alpha3: str, country=None, script=None):
|
||||||
|
return str(Language(alpha3, country, script))
|
||||||
|
|
||||||
|
def reverse(self, name: str):
|
||||||
|
try:
|
||||||
|
return self.synonyms[name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for conv in (Language.fromname,):
|
||||||
|
try:
|
||||||
|
reverse = conv(name)
|
||||||
|
return reverse.alpha3, reverse.country, reverse.script
|
||||||
|
except (ValueError, LanguageReverseError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise LanguageReverseError(name)
|
@ -0,0 +1,169 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
from babelfish import (
|
||||||
|
COUNTRIES,
|
||||||
|
Country,
|
||||||
|
CountryReverseError,
|
||||||
|
LANGUAGE_MATRIX,
|
||||||
|
Language,
|
||||||
|
LanguageReverseError,
|
||||||
|
SCRIPTS,
|
||||||
|
Script,
|
||||||
|
country_converters,
|
||||||
|
language_converters
|
||||||
|
)
|
||||||
|
from babelfish.converters import CaseInsensitiveDict
|
||||||
|
|
||||||
|
from rebulk import Rebulk
|
||||||
|
from rebulk.match import Match
|
||||||
|
|
||||||
|
from trakit.config import Config
|
||||||
|
from trakit.context import Context
|
||||||
|
from trakit.converters.country import GuessCountryConverter
|
||||||
|
from trakit.converters.language import GuessLanguageConverter
|
||||||
|
from trakit.words import blank_match, blank_release_names, to_combinations, to_match, to_sentence, to_words
|
||||||
|
|
||||||
|
|
||||||
|
class LanguageFinder:
|
||||||
|
|
||||||
|
def __init__(self, config: Config):
|
||||||
|
self.country_max_words = 1
|
||||||
|
for k, v in COUNTRIES.items():
|
||||||
|
self.country_max_words = max(self.country_max_words, v.count(' '))
|
||||||
|
|
||||||
|
self.language_max_words = 1
|
||||||
|
for v in LANGUAGE_MATRIX:
|
||||||
|
self.language_max_words = max(self.language_max_words, v.name.count(' '))
|
||||||
|
|
||||||
|
self.script_max_words = 1
|
||||||
|
for v in config.scripts.keys():
|
||||||
|
self.script_max_words = max(self.script_max_words, v.count(' '))
|
||||||
|
|
||||||
|
self.region_max_words = 1
|
||||||
|
for v in config.regions.keys():
|
||||||
|
self.region_max_words = max(self.region_max_words, v.count(' '))
|
||||||
|
|
||||||
|
SCRIPTS['419'] = 'Latin America and the Caribbean' # Until babelfish support UN.M49
|
||||||
|
country_converters['guess'] = GuessCountryConverter(config.countries)
|
||||||
|
language_converters['guess'] = GuessLanguageConverter(config.languages)
|
||||||
|
self.regions = CaseInsensitiveDict(config.regions)
|
||||||
|
self.scripts = CaseInsensitiveDict(config.scripts)
|
||||||
|
self.common_words = CaseInsensitiveDict(dict.fromkeys(config.ignored, 0))
|
||||||
|
self.implicit = CaseInsensitiveDict(config.implicit_languages)
|
||||||
|
|
||||||
|
def _find_country(self, value: str):
|
||||||
|
combinations = to_combinations(to_words(value), self.country_max_words)
|
||||||
|
for c in combinations:
|
||||||
|
code = to_sentence(c)
|
||||||
|
try:
|
||||||
|
return to_match(c, Country.fromguess(code))
|
||||||
|
except CountryReverseError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _find_script(self, value: str):
|
||||||
|
combinations = to_combinations(to_words(value), self.script_max_words)
|
||||||
|
for c in combinations:
|
||||||
|
code = to_sentence(c)
|
||||||
|
try:
|
||||||
|
return to_match(c, Script(self.scripts.get(code, code)))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _find_region(self, value: str):
|
||||||
|
combinations = to_combinations(to_words(value), self.region_max_words)
|
||||||
|
for c in combinations:
|
||||||
|
code = to_sentence(c)
|
||||||
|
try:
|
||||||
|
return to_match(c, Script(self.regions.get(code, code)))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _find_implicit_language(self, combinations: typing.List[typing.List[Match]]):
|
||||||
|
for c in combinations:
|
||||||
|
sentence = to_sentence(c)
|
||||||
|
if sentence in self.implicit:
|
||||||
|
return to_match(c, Language.fromietf(self.implicit[sentence]))
|
||||||
|
|
||||||
|
region = self._find_region(sentence)
|
||||||
|
if region and region.value.code in self.implicit:
|
||||||
|
lang = Language.fromietf(self.implicit[region.value.code])
|
||||||
|
return Match(region.start, region.end, value=lang, input_string=region.input_string)
|
||||||
|
|
||||||
|
try:
|
||||||
|
country = Country.fromguess(sentence)
|
||||||
|
if country.alpha2 in self.implicit:
|
||||||
|
lang = Language.fromietf(self.implicit[country.alpha2])
|
||||||
|
if lang.name.lower() == sentence.lower():
|
||||||
|
lang = Language.fromname(sentence)
|
||||||
|
|
||||||
|
return to_match(c, lang)
|
||||||
|
except CountryReverseError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def accept_word(self, string: str):
|
||||||
|
return string.lower() not in self.common_words and not string.isnumeric()
|
||||||
|
|
||||||
|
def find_language(self, value: str, context: Context):
|
||||||
|
value = blank_release_names(value)
|
||||||
|
all_words = to_words(value, predicate=self.accept_word)
|
||||||
|
combinations = to_combinations(all_words, self.language_max_words)
|
||||||
|
implicit_lang = self._find_implicit_language(combinations)
|
||||||
|
implicit_accepted = implicit_lang and context.accept(implicit_lang.value)
|
||||||
|
|
||||||
|
if implicit_accepted and implicit_lang.value.script and implicit_lang.value.script.code.isnumeric():
|
||||||
|
return implicit_lang
|
||||||
|
elif implicit_lang and not implicit_accepted:
|
||||||
|
value = blank_match(implicit_lang)
|
||||||
|
all_words = to_words(value, predicate=self.accept_word)
|
||||||
|
combinations = to_combinations(all_words, self.language_max_words)
|
||||||
|
|
||||||
|
for c in combinations:
|
||||||
|
language_sentence = to_sentence(c)
|
||||||
|
try:
|
||||||
|
lang = Language.fromguess(language_sentence)
|
||||||
|
except LanguageReverseError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
match_lang = to_match(c, lang)
|
||||||
|
remaining_sentence = blank_match(match_lang)
|
||||||
|
for combination in to_combinations(to_words(remaining_sentence), self.country_max_words):
|
||||||
|
sentence = to_sentence(combination)
|
||||||
|
country = self._find_country(sentence)
|
||||||
|
if country:
|
||||||
|
try:
|
||||||
|
# discard country if value is actually the language name
|
||||||
|
Language.fromguess(country.raw)
|
||||||
|
except LanguageReverseError:
|
||||||
|
lang = Language(lang.alpha3, country=country.value, script=lang.script)
|
||||||
|
break
|
||||||
|
|
||||||
|
region = self._find_region(sentence)
|
||||||
|
if region:
|
||||||
|
lang = Language(lang.alpha3, country=lang.country, script=region.value)
|
||||||
|
break
|
||||||
|
|
||||||
|
script = self._find_script(sentence)
|
||||||
|
if script:
|
||||||
|
lang = Language(lang.alpha3, country=lang.country, script=script.value)
|
||||||
|
break
|
||||||
|
|
||||||
|
if implicit_accepted and implicit_lang.value.alpha3 == lang.alpha3 and not lang.country and not lang.script:
|
||||||
|
return implicit_lang
|
||||||
|
|
||||||
|
if context.accept(lang):
|
||||||
|
return to_match(c, lang)
|
||||||
|
|
||||||
|
if implicit_accepted:
|
||||||
|
return implicit_lang
|
||||||
|
|
||||||
|
def find(self, value: str, context: Context):
|
||||||
|
match = self.find_language(value, context)
|
||||||
|
if match:
|
||||||
|
return match.start, match.end, {'value': match.value}
|
||||||
|
|
||||||
|
|
||||||
|
def language(config: Config):
|
||||||
|
rebulk = Rebulk()
|
||||||
|
rebulk.functional(LanguageFinder(config).find, name='language')
|
||||||
|
|
||||||
|
return rebulk
|
@ -0,0 +1,32 @@
|
|||||||
|
import re
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from rebulk import Rebulk
|
||||||
|
from rebulk.validators import chars_surround
|
||||||
|
|
||||||
|
from trakit.config import Config
|
||||||
|
from trakit.language import language
|
||||||
|
from trakit.words import seps
|
||||||
|
|
||||||
|
|
||||||
|
def configure(config: Config):
|
||||||
|
seps_surround = partial(chars_surround, seps)
|
||||||
|
|
||||||
|
others = Rebulk()
|
||||||
|
others.defaults(ignore_case=True, validator=seps_surround)
|
||||||
|
others.regex_defaults(flags=re.IGNORECASE,
|
||||||
|
abbreviations=[(r'-', rf'[{re.escape("".join(seps))}]')],
|
||||||
|
validator=seps_surround)
|
||||||
|
for name in ('forced', 'commentary', 'external'):
|
||||||
|
others.string(name, name=name, value=True)
|
||||||
|
|
||||||
|
others.string('sdh', name='hearing_impaired', value=True)
|
||||||
|
others.string('alternate', name='version', value='alternate')
|
||||||
|
others.string('descriptive', name='descriptive', value=True)
|
||||||
|
others.regex('cc', 'closed-captions?', name='closed_caption', value=True)
|
||||||
|
|
||||||
|
rebulk = Rebulk()
|
||||||
|
rebulk.rebulk(language(config))
|
||||||
|
rebulk.rebulk(others)
|
||||||
|
|
||||||
|
return rebulk
|
@ -0,0 +1,99 @@
|
|||||||
|
import re
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from rebulk.match import Match
|
||||||
|
|
||||||
|
seps = frozenset(r' [](){}+*|=-_~#/\\.,;:' + '\uff08\uff09')
|
||||||
|
suppress_chars = frozenset("'")
|
||||||
|
release_name_re = re.compile(r'(?P<release>[^\.\s]+(?:\.[^\.\s]+){2,})')
|
||||||
|
|
||||||
|
|
||||||
|
def to_words(value: str,
|
||||||
|
separators: typing.FrozenSet[str] = seps,
|
||||||
|
ignore_chars: typing.FrozenSet[str] = suppress_chars,
|
||||||
|
predicate: typing.Callable[[str], bool] = lambda x: True):
|
||||||
|
input_string = value
|
||||||
|
start = 0
|
||||||
|
i = 0
|
||||||
|
word = ''
|
||||||
|
words: typing.List[Match] = []
|
||||||
|
for c in input_string:
|
||||||
|
i += 1
|
||||||
|
if c in ignore_chars:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if c not in separators:
|
||||||
|
word += c
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not word:
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
|
||||||
|
end = i - 1
|
||||||
|
if not predicate(value[start:end]):
|
||||||
|
input_string = blank(input_string, start, end)
|
||||||
|
else:
|
||||||
|
words.append(Match(start, i - 1, value=word))
|
||||||
|
|
||||||
|
word = ''
|
||||||
|
start = i
|
||||||
|
|
||||||
|
if word:
|
||||||
|
if not predicate(value[start:]):
|
||||||
|
input_string = blank(input_string, start, len(input_string))
|
||||||
|
else:
|
||||||
|
words.append(Match(start, i, value=word))
|
||||||
|
|
||||||
|
for w in words:
|
||||||
|
w.input_string = input_string
|
||||||
|
|
||||||
|
return words
|
||||||
|
|
||||||
|
|
||||||
|
def to_combinations(words: typing.List[Match], max_items: int):
|
||||||
|
results: typing.List[typing.List[Match]] = []
|
||||||
|
n_words = len(words)
|
||||||
|
cur_size = min(max_items, n_words)
|
||||||
|
start = 0
|
||||||
|
while cur_size > 0:
|
||||||
|
end = start + cur_size
|
||||||
|
if end > n_words:
|
||||||
|
start = 0
|
||||||
|
cur_size -= 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
results.append(words[start:end])
|
||||||
|
start += 1
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def to_sentence(combination: typing.List[Match]):
|
||||||
|
return ' '.join([c.value for c in combination])
|
||||||
|
|
||||||
|
|
||||||
|
def to_match(combination: typing.List[Match], value: typing.Any):
|
||||||
|
start = combination[0].start
|
||||||
|
end = combination[-1].end
|
||||||
|
input_string = combination[0].input_string
|
||||||
|
|
||||||
|
return Match(start, end, value=value, input_string=input_string)
|
||||||
|
|
||||||
|
|
||||||
|
def blank(string: str, start: int, end: int):
|
||||||
|
return string[:start] + ''.ljust(end - start, ' ') + string[end:]
|
||||||
|
|
||||||
|
|
||||||
|
def blank_match(match: Match):
|
||||||
|
return blank(match.input_string, match.start, match.end)
|
||||||
|
|
||||||
|
|
||||||
|
def blank_release_names(value: str):
|
||||||
|
result = value
|
||||||
|
match = release_name_re.search(value)
|
||||||
|
while match:
|
||||||
|
result = blank(result, match.start('release'), match.end('release'))
|
||||||
|
match = release_name_re.search(value, match.end('release'))
|
||||||
|
|
||||||
|
return result
|
Loading…
Reference in new issue