diff options
author | morpheus65535 <[email protected]> | 2023-03-21 23:15:01 -0400 |
---|---|---|
committer | morpheus65535 <[email protected]> | 2023-03-21 23:15:01 -0400 |
commit | 7455496c4c42518df5f20646d50a93ca66c1a912 (patch) | |
tree | f7992557e15e6d8c8494edb2789772aa4b0dce44 /libs/knowit | |
parent | 71363830985a34f5f45a32972477e0ac83dce519 (diff) | |
download | bazarr-7455496c4c42518df5f20646d50a93ca66c1a912.tar.gz bazarr-7455496c4c42518df5f20646d50a93ca66c1a912.zip |
Trying to fix Segmentation fault caused by mediainfo in docker container. #2098v1.2.1-beta.9
Diffstat (limited to 'libs/knowit')
-rw-r--r-- | libs/knowit/__init__.py | 5 | ||||
-rw-r--r-- | libs/knowit/__main__.py | 2 | ||||
-rw-r--r-- | libs/knowit/api.py | 2 | ||||
-rw-r--r-- | libs/knowit/core.py | 16 | ||||
-rw-r--r-- | libs/knowit/defaults.yml | 28 | ||||
-rw-r--r-- | libs/knowit/properties/video.py | 9 | ||||
-rwxr-xr-x[-rw-r--r--] | libs/knowit/provider.py | 10 | ||||
-rw-r--r-- | libs/knowit/providers/enzyme.py | 17 | ||||
-rw-r--r-- | libs/knowit/providers/ffmpeg.py | 24 | ||||
-rw-r--r-- | libs/knowit/providers/mediainfo.py | 45 | ||||
-rw-r--r-- | libs/knowit/providers/mkvmerge.py | 10 | ||||
-rw-r--r-- | libs/knowit/rules/general.py | 33 | ||||
-rw-r--r-- | libs/knowit/rules/subtitle.py | 17 | ||||
-rw-r--r-- | libs/knowit/units.py | 20 |
14 files changed, 146 insertions, 92 deletions
diff --git a/libs/knowit/__init__.py b/libs/knowit/__init__.py index eda706779..bf225e195 100644 --- a/libs/knowit/__init__.py +++ b/libs/knowit/__init__.py @@ -1,10 +1,9 @@ """Know your media files better.""" __title__ = 'knowit' -__version__ = '0.4.0' -__short_version__ = '.'.join(__version__.split('.')[:2]) +__version__ = '0.5.2' +__short_version__ = '0.5' __author__ = 'Rato AQ2' __license__ = 'MIT' -__copyright__ = 'Copyright 2016-2021, Rato AQ2' __url__ = 'https://github.com/ratoaq2/knowit' #: Video extensions diff --git a/libs/knowit/__main__.py b/libs/knowit/__main__.py index c30148421..d9255ffd4 100644 --- a/libs/knowit/__main__.py +++ b/libs/knowit/__main__.py @@ -169,7 +169,7 @@ def dumps( return convert(info, context) -def main(args: typing.List[str] = None) -> None: +def main(args: typing.Optional[typing.List[str]] = None) -> None: """Execute main function for entry point.""" argument_parser = build_argument_parser() args = args or sys.argv[1:] diff --git a/libs/knowit/api.py b/libs/knowit/api.py index 4df780605..c6ebd3bd6 100644 --- a/libs/knowit/api.py +++ b/libs/knowit/api.py @@ -65,7 +65,7 @@ def know( raise KnowitException(debug_info(context=context, exc_info=True)) -def dependencies(context: typing.Mapping = None) -> typing.Mapping: +def dependencies(context: typing.Optional[typing.Mapping] = None) -> typing.Mapping: """Return all dependencies detected by knowit.""" deps = {} try: diff --git a/libs/knowit/core.py b/libs/knowit/core.py index 9736d7ba2..ede307dad 100644 --- a/libs/knowit/core.py +++ b/libs/knowit/core.py @@ -63,6 +63,17 @@ class Property(Reportable[T]): # Used to detect duplicated values. e.g.: en / en or [email protected] / [email protected] or Progressive / Progressive self.delimiter = delimiter + @classmethod + def _extract_value(cls, + track: typing.Mapping, + name: str, + names: typing.List[str]): + if len(names) == 2: + parent_value = track.get(names[0], track.get(names[0].upper(), {})) + return parent_value.get(names[1], parent_value.get(names[1].upper())) + + return track.get(name, track.get(name.upper())) + def extract_value( self, track: typing.Mapping, @@ -71,7 +82,7 @@ class Property(Reportable[T]): """Extract the property value from a given track.""" for name in self.names: names = name.split('.') - value = track.get(names[0], {}).get(names[1]) if len(names) == 2 else track.get(name) + value = self._extract_value(track, name, names) if value is None: if self.default is None: continue @@ -216,9 +227,10 @@ class MultiValue(Property): class Rule(Reportable[T]): """Rule abstract class.""" - def __init__(self, name: str, override=False, **kwargs): + def __init__(self, name: str, private=False, override=False, **kwargs): """Initialize the object.""" super().__init__(name, **kwargs) + self.private = private self.override = override def execute(self, props, pv_props, context: typing.Mapping): diff --git a/libs/knowit/defaults.yml b/libs/knowit/defaults.yml index 9dd7e46db..af6b79c32 100644 --- a/libs/knowit/defaults.yml +++ b/libs/knowit/defaults.yml @@ -455,46 +455,46 @@ profiles: VideoProfileLevel: L1: - default: "1" + default: '1' technical: Level 1 L11: - default: "1.1" + default: '1.1' technical: Level 1.1 L13: - default: "1.3" + default: '1.3' technical: Level 1.3 L2: - default: "2" + default: '2' technical: Level 2 L21: - default: "2.1" + default: '2.1' technical: Level 2.1 L22: - default: "2.2" + default: '2.2' technical: Level 2.2 L3: - default: "3" + default: '3' technical: Level 3 L31: - default: "3.1" + default: '3.1' technical: Level 3.1 L32: - default: "3.2" + default: '3.2' technical: Level 3.2 L4: - default: "4" + default: '4' technical: Level 4 L41: - default: "4.1" + default: '4.1' technical: Level 4.1 L42: - default: "4.2" + default: '4.2' technical: Level 4.2 L5: - default: "5" + default: '5' technical: Level 5 L51: - default: "5.1" + default: '5.1' technical: Level 5.1 LOW: default: Low diff --git a/libs/knowit/properties/video.py b/libs/knowit/properties/video.py index e1b293d01..60c5b8264 100644 --- a/libs/knowit/properties/video.py +++ b/libs/knowit/properties/video.py @@ -106,11 +106,12 @@ class Ratio(Property[Decimal]): if (width, height) == ('0', '1'): # identity return Decimal('1.0') - result = round_decimal(Decimal(width) / Decimal(height), min_digits=1, max_digits=3) - if self.unit: - result *= self.unit + if height: + result = round_decimal(Decimal(width) / Decimal(height), min_digits=1, max_digits=3) + if self.unit: + result *= self.unit - return result + return result self.report(value, context) return None diff --git a/libs/knowit/provider.py b/libs/knowit/provider.py index f8c29f5f3..5306d8388 100644..100755 --- a/libs/knowit/provider.py +++ b/libs/knowit/provider.py @@ -103,10 +103,7 @@ class Provider: value = prop.extract_value(track, context) if value is not None: - if not prop.private: - which = props - else: - which = pv_props + which = props if not prop.private else pv_props which[name] = value for name, rule in self.rules.get(track_type, {}).items(): @@ -116,8 +113,9 @@ class Provider: value = rule.execute(props, pv_props, context) if value is not None: - props[name] = value - elif name in props and not rule.override: + which = props if not rule.private else pv_props + which[name] = value + elif name in props and (not rule.override or props[name] is None): del props[name] return props diff --git a/libs/knowit/providers/enzyme.py b/libs/knowit/providers/enzyme.py index 5dd3d8cef..6a06599d4 100644 --- a/libs/knowit/providers/enzyme.py +++ b/libs/knowit/providers/enzyme.py @@ -26,6 +26,7 @@ from knowit.rules import ( LanguageRule, ResolutionRule, ) +from knowit.rules.general import GuessTitleRule from knowit.serializer import get_json_encoder from knowit.units import units from knowit.utils import to_dict @@ -83,17 +84,20 @@ class EnzymeProvider(Provider): }, }, { 'video': { - 'language': LanguageRule('video language'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('video language', override=True), 'resolution': ResolutionRule('video resolution'), }, 'audio': { - 'language': LanguageRule('audio language'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('audio language', override=True), 'channels': AudioChannelsRule('audio channels'), }, 'subtitle': { - 'language': LanguageRule('subtitle language'), - 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), - 'closed_caption': ClosedCaptionRule('closed caption'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('subtitle language', override=True), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired', override=True), + 'closed_caption': ClosedCaptionRule('closed caption', override=True), } }) @@ -130,7 +134,8 @@ class EnzymeProvider(Provider): if logger.level == logging.DEBUG: logger.debug('Video {video_path} scanned using Enzyme {version} has raw data:\n{data}', - video_path=video_path, version=enzyme.__version__, data=json.dumps(data)) + video_path=video_path, version=enzyme.__version__, + data=json.dumps(data, cls=get_json_encoder(context), indent=4, ensure_ascii=False)) result = self._describe_tracks(video_path, data.get('info', {}), data.get('video_tracks'), data.get('audio_tracks'), data.get('subtitle_tracks'), context) diff --git a/libs/knowit/providers/ffmpeg.py b/libs/knowit/providers/ffmpeg.py index 2474408cc..f19cea90b 100644 --- a/libs/knowit/providers/ffmpeg.py +++ b/libs/knowit/providers/ffmpeg.py @@ -34,6 +34,7 @@ from knowit.rules import ( LanguageRule, ResolutionRule, ) +from knowit.rules.general import GuessTitleRule from knowit.serializer import get_json_encoder from knowit.units import units from knowit.utils import ( @@ -77,7 +78,7 @@ class FFmpegExecutor: def extract_info(self, filename): """Extract media info.""" json_dump = self._execute(filename) - return json.loads(json_dump) + return json.loads(json_dump) if json_dump else {} def _execute(self, filename): raise NotImplementedError @@ -144,7 +145,7 @@ class FFmpegProvider(Provider): 'id': Basic('index', data_type=int, allow_fallback=True, description='video track number'), 'name': Property('tags.title', description='video track name'), 'language': Language('tags.language', description='video language'), - 'duration': Duration('duration', description='video duration'), + 'duration': Duration('duration', 'tags.duration', description='video duration'), 'width': Quantity('width', unit=units.pixel), 'height': Quantity('height', unit=units.pixel), 'scan_type': ScanType(config, 'field_order', default='Progressive', description='video scan type'), @@ -153,7 +154,7 @@ class FFmpegProvider(Provider): 'resolution': None, # populated with ResolutionRule 'frame_rate': Ratio('r_frame_rate', unit=units.FPS, description='video frame rate'), # frame_rate_mode - 'bit_rate': Quantity('bit_rate', unit=units.bps, description='video bit rate'), + 'bit_rate': Quantity('bit_rate', 'tags.bps', unit=units.bps, description='video bit rate'), 'bit_depth': Quantity('bits_per_raw_sample', unit=units.bit, description='video bit depth'), 'codec': VideoCodec(config, 'codec_name', description='video codec'), 'profile': VideoProfile(config, 'profile', description='video codec profile'), @@ -166,13 +167,13 @@ class FFmpegProvider(Provider): 'id': Basic('index', data_type=int, allow_fallback=True, description='audio track number'), 'name': Property('tags.title', description='audio track name'), 'language': Language('tags.language', description='audio language'), - 'duration': Duration('duration', description='audio duration'), + 'duration': Duration('duration', 'tags.duration', description='audio duration'), 'codec': AudioCodec(config, 'profile', 'codec_name', description='audio codec'), 'profile': AudioProfile(config, 'profile', description='audio codec profile'), 'channels_count': AudioChannels('channels', description='audio channels count'), 'channels': None, # populated with AudioChannelsRule 'bit_depth': Quantity('bits_per_raw_sample', unit=units.bit, description='audio bit depth'), - 'bit_rate': Quantity('bit_rate', unit=units.bps, description='audio bit rate'), + 'bit_rate': Quantity('bit_rate', 'tags.bps', unit=units.bps, description='audio bit rate'), 'sampling_rate': Quantity('sample_rate', unit=units.Hz, description='audio sampling rate'), 'forced': YesNo('disposition.forced', hide_value=False, description='audio track forced'), 'default': YesNo('disposition.default', hide_value=False, description='audio track default'), @@ -190,17 +191,20 @@ class FFmpegProvider(Provider): }, }, { 'video': { - 'language': LanguageRule('video language'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('video language', override=True), 'resolution': ResolutionRule('video resolution'), }, 'audio': { - 'language': LanguageRule('audio language'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('audio language', override=True), 'channels': AudioChannelsRule('audio channels'), }, 'subtitle': { - 'language': LanguageRule('subtitle language'), - 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), - 'closed_caption': ClosedCaptionRule('closed caption'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('subtitle language', override=True), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired', override=True), + 'closed_caption': ClosedCaptionRule('closed caption', override=True), }, }) self.executor = FFmpegExecutor.get_executor_instance(suggested_path) diff --git a/libs/knowit/providers/mediainfo.py b/libs/knowit/providers/mediainfo.py index 39fd403ed..a19301bc6 100644 --- a/libs/knowit/providers/mediainfo.py +++ b/libs/knowit/providers/mediainfo.py @@ -1,5 +1,6 @@ - +import ctypes import json +import os import re from ctypes import c_void_p, c_wchar_p from decimal import Decimal @@ -43,6 +44,7 @@ from knowit.rules import ( LanguageRule, ResolutionRule, ) +from knowit.rules.general import GuessTitleRule from knowit.units import units from knowit.utils import ( define_candidate, @@ -77,7 +79,7 @@ class MediaInfoExecutor: locations = { 'unix': ('/usr/local/mediainfo/lib', '/usr/local/mediainfo/bin', '__PATH__'), - 'windows': ('__PATH__', ), + 'windows': ('C:\\Program Files\\MediaInfo', 'C:\\Program Files (x86)\\MediaInfo', '__PATH__'), 'macos': ('__PATH__', ), } @@ -121,12 +123,28 @@ class MediaInfoCliExecutor(MediaInfoExecutor): } def _execute(self, filename): - return json.loads(check_output([self.location, '--Output=JSON', '--Full', filename]).decode()) + data = check_output([self.location, '--Output=JSON', '--Full', filename]).decode() + + return json.loads(data) if data else {} + + @classmethod + def _is_gui_exe(cls, candidate: str): + if not candidate.endswith('MediaInfo.exe') or not os.path.isfile(candidate): + return False + + try: + shell32 = ctypes.WinDLL('shell32', use_last_error=True) # type: ignore + return bool(shell32.ExtractIconExW(candidate, 0, None, None, 1)) + except Exception: + return False @classmethod def create(cls, os_family=None, suggested_path=None): """Create the executor instance.""" for candidate in define_candidate(cls.locations, cls.names, os_family, suggested_path): + if cls._is_gui_exe(candidate): + continue + try: output = check_output([candidate, '--version']).decode() version = cls._get_version(output) @@ -154,7 +172,9 @@ class MediaInfoCTypesExecutor(MediaInfoExecutor): def _execute(self, filename): # Create a MediaInfo handle - return json.loads(MediaInfo.parse(filename, library_file=self.location, output='JSON')) + data = MediaInfo.parse(filename, library_file=self.location, output='JSON') + + return json.loads(data) if data else {} @classmethod def create(cls, os_family=None, suggested_path=None): @@ -254,19 +274,22 @@ class MediaInfoProvider(Provider): }, }, { 'video': { - 'language': LanguageRule('video language'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('video language', override=True), 'resolution': ResolutionRule('video resolution'), }, 'audio': { - 'language': LanguageRule('audio language'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('audio language', override=True), 'channels': AudioChannelsRule('audio channels'), - '_atmosrule': AtmosRule(config, 'atmos rule'), - '_dtshdrule': DtsHdRule(config, 'dts-hd rule'), + 'atmos': AtmosRule(config, 'atmos rule', private=True), + 'dtshd': DtsHdRule(config, 'dts-hd rule', private=True), }, 'subtitle': { - 'language': LanguageRule('subtitle language'), - 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), - 'closed_caption': ClosedCaptionRule('closed caption'), + 'guessed': GuessTitleRule('guessed properties', private=True), + 'language': LanguageRule('subtitle language', override=True), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired', override=True), + 'closed_caption': ClosedCaptionRule('closed caption', override=True), } }) self.executor = MediaInfoExecutor.get_executor_instance(suggested_path) diff --git a/libs/knowit/providers/mkvmerge.py b/libs/knowit/providers/mkvmerge.py index e5aca1550..ff422f8b4 100644 --- a/libs/knowit/providers/mkvmerge.py +++ b/libs/knowit/providers/mkvmerge.py @@ -28,6 +28,7 @@ from knowit.rules import ( LanguageRule, ResolutionRule, ) +from knowit.rules.general import GuessTitleRule from knowit.serializer import get_json_encoder from knowit.units import units from knowit.utils import define_candidate, detect_os @@ -67,7 +68,7 @@ class MkvMergeExecutor: def extract_info(self, filename): """Extract media info.""" json_dump = self._execute(filename) - return json.loads(json_dump) + return json.loads(json_dump) if json_dump else {} def _execute(self, filename): raise NotImplementedError @@ -166,17 +167,20 @@ class MkvMergeProvider(Provider): }, }, { 'video': { + 'guessed': GuessTitleRule('guessed properties', private=True), 'language': LanguageRule('video language', override=True), 'resolution': ResolutionRule('video resolution'), }, 'audio': { + 'guessed': GuessTitleRule('guessed properties', private=True), 'language': LanguageRule('audio language', override=True), 'channels': AudioChannelsRule('audio channels'), }, 'subtitle': { + 'guessed': GuessTitleRule('guessed properties', private=True), 'language': LanguageRule('subtitle language', override=True), - 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), - 'closed_caption': ClosedCaptionRule('closed caption'), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired', override=True), + 'closed_caption': ClosedCaptionRule('closed caption', override=True), } }) self.executor = MkvMergeExecutor.get_executor_instance(suggested_path) diff --git a/libs/knowit/rules/general.py b/libs/knowit/rules/general.py index b492c03a5..ad2c7734f 100644 --- a/libs/knowit/rules/general.py +++ b/libs/knowit/rules/general.py @@ -1,8 +1,6 @@ - -import re from logging import NullHandler, getLogger -import babelfish +from trakit.api import trakit from knowit.core import Rule @@ -10,22 +8,27 @@ logger = getLogger(__name__) logger.addHandler(NullHandler()) +class GuessTitleRule(Rule): + """Guess properties from track title.""" + + def execute(self, props, pv_props, context): + """Language detection using name.""" + if 'name' in props: + language = props.get('language') + options = {'expected_language': language} if language else {} + guessed = trakit(props['name'], options) + if guessed: + return guessed + + class LanguageRule(Rule): """Language rules.""" - name_re = re.compile(r'(?P<name>\w+)\b', re.IGNORECASE) - def execute(self, props, pv_props, context): """Language detection using name.""" - if 'language' in props: + if 'guessed' not in pv_props: return - if 'name' in props: - name = props.get('name', '') - match = self.name_re.match(name) - if match: - try: - return babelfish.Language.fromname(match.group('name')) - except babelfish.Error: - pass - logger.info('Invalid %s: %r', self.description, name) + guess = pv_props['guessed'] + if 'language' in guess: + return guess['language'] diff --git a/libs/knowit/rules/subtitle.py b/libs/knowit/rules/subtitle.py index fa16fdbc1..704109f99 100644 --- a/libs/knowit/rules/subtitle.py +++ b/libs/knowit/rules/subtitle.py @@ -10,18 +10,19 @@ class ClosedCaptionRule(Rule): def execute(self, props, pv_props, context): """Execute closed caption rule.""" - for name in (pv_props.get('_closed_caption'), props.get('name')): - if name and self.cc_re.search(name): - return True + if '_closed_caption' in pv_props and self.cc_re.search(pv_props['_closed_caption']): + return True + + if 'guessed' in pv_props: + guessed = pv_props['guessed'] + return guessed.get('closed_caption') class HearingImpairedRule(Rule): """Hearing Impaired rule.""" - hi_re = re.compile(r'(\bsdh\b)', re.IGNORECASE) - def execute(self, props, pv_props, context): """Hearing Impaired.""" - name = props.get('name') - if name and self.hi_re.search(name): - return True + if 'guessed' in pv_props: + guessed = pv_props['guessed'] + return guessed.get('hearing_impaired') diff --git a/libs/knowit/units.py b/libs/knowit/units.py index 73ec16a5a..51e6cae73 100644 --- a/libs/knowit/units.py +++ b/libs/knowit/units.py @@ -1,10 +1,5 @@ import typing -try: - import pint -except ImportError: - pint = False - class NullRegistry: """A NullRegistry that masquerades as a pint.UnitRegistry.""" @@ -25,9 +20,18 @@ class NullRegistry: def _build_unit_registry(): - registry = pint.UnitRegistry() if pint else NullRegistry() - registry.define('FPS = 1 * hertz') - return registry + try: + import pint + + registry = pint.UnitRegistry() + registry.define('FPS = 1 * hertz') + + pint.set_application_registry(registry) + return registry + except ModuleNotFoundError: + pass + + return NullRegistry() units = _build_unit_registry() |