summaryrefslogtreecommitdiffhomepage
path: root/libs/knowit
diff options
context:
space:
mode:
authormorpheus65535 <[email protected]>2023-03-21 23:15:01 -0400
committermorpheus65535 <[email protected]>2023-03-21 23:15:01 -0400
commit7455496c4c42518df5f20646d50a93ca66c1a912 (patch)
treef7992557e15e6d8c8494edb2789772aa4b0dce44 /libs/knowit
parent71363830985a34f5f45a32972477e0ac83dce519 (diff)
downloadbazarr-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__.py5
-rw-r--r--libs/knowit/__main__.py2
-rw-r--r--libs/knowit/api.py2
-rw-r--r--libs/knowit/core.py16
-rw-r--r--libs/knowit/defaults.yml28
-rw-r--r--libs/knowit/properties/video.py9
-rwxr-xr-x[-rw-r--r--]libs/knowit/provider.py10
-rw-r--r--libs/knowit/providers/enzyme.py17
-rw-r--r--libs/knowit/providers/ffmpeg.py24
-rw-r--r--libs/knowit/providers/mediainfo.py45
-rw-r--r--libs/knowit/providers/mkvmerge.py10
-rw-r--r--libs/knowit/rules/general.py33
-rw-r--r--libs/knowit/rules/subtitle.py17
-rw-r--r--libs/knowit/units.py20
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()