From fc32c05a9ab51b19196612c3256c4a6c8428645f Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 01:23:16 -0400 Subject: [PATCH 001/102] Refactor installation and testing Drop support for EOL python Drop deprecated pytest-runner in favor of using pytest directly Drop extras-require and add them to tox dependencies Fix babelfish min version for Python >= 3.10 Add support for PEP-517 with pyproject.toml Add setup.cfg installation with a setup.py shim for editable installs Add mypy support for type-hints Add mypy configuration to setup.cfg Add flake8 configuration to setup.cfg Add tox-travis to use tox during travis runs --- .gitignore | 1 + .travis.yml | 6 ++-- pyproject.toml | 6 ++++ pytest.ini | 15 ++++---- setup.cfg | 92 ++++++++++++++++++++++++++++++++++++++++++++++++-- setup.py | 81 ++------------------------------------------ tox.ini | 30 +++++++++++++--- 7 files changed, 133 insertions(+), 98 deletions(-) create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index b7851dc..dd69a8c 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ celerybeat-schedule .env # virtualenv +.venv*/ venv*/ ENV/ diff --git a/.travis.yml b/.travis.yml index 8524cda..2d6cc17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,6 @@ language: python dist: xenial python: - - 2.7 - - 3.4 - - 3.5 - 3.6 - 3.7 - 3.8 @@ -33,6 +30,7 @@ before_install: - pip install --upgrade pytest install: + - pip install tox-travis - pip install -e . -script: python setup.py test --addopts "--verbose $PYTEST_ADDOPTS" +script: tox diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f10a11c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools >= 40.6.0", + "wheel", +] +build-backend = "setuptools.build_meta" diff --git a/pytest.ini b/pytest.ini index e1c11eb..edf52ae 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,10 +1,9 @@ [pytest] -norecursedirs = venv* .cache .tox .eggs -addopts = --flake8 +norecursedirs = + .cache + .eggs + .tox + .venv* + .mypy_cache + venv* python_files = *test*.py -flake8-max-line-length = 120 -flake8-ignore = - * D100 D103 I201 - knowit/__init__.py E402 F401 - knowit/*/__init__.py D104 F401 - knowit/*/*/__init__.py D104 F401 diff --git a/setup.cfg b/setup.cfg index b7e4789..c4b27e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,90 @@ -[aliases] -test=pytest +[metadata] +name = knowit +version = attr: knowit.__version__ +description = Know your media files better +long_description = file: README.rst +author = Rato AQ2 +author_email = rato.aq2@gmail.com +url = https://github.com/ratoaq2/knowit +license = MIT +keywords = + episode + mediainfo + metadata + mkv + mp4 + movie + series + shows + tv + video +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Topic :: Software Development :: Libraries :: Python Modules + Topic :: Multimedia :: Video + +[options] +include_package_data = True +packages = find: +install_requires = + babelfish >= 0.5.5 ; python_version < "3.10" + babelfish >= 0.5.6 ; python_version >= "3.10" + enzyme >= 0.4.1 + pint >= 0.9 + pymediainfo >= 3.0 + PyYAML >= 3.13 + six >= 1.12.0 + +[options.packages.find] +exclude = + tests + docs + +[options.entry_points] +console_scripts = + knowit = knowit.__main__:main + +[flake8] +import-order-style = cryptography +application-import-names = knowit +max-line-length = 120 +ignore = + # D100 Missing docstring in public module + D100 + # D103 Missing docstring in public function + D103 + # I201 Missing newline between import groups + I201 +per-file-ignores = + __init__.py: + # D104 Missing docstring in public package + D104 + # F401 Imported but unused + F401 + knowit/__init__.py: + # E402 Module level import not at top of file + E402 + # F401 Imported but unused + F401 + +[mypy] + +[mypy-pint.*] +ignore_missing_imports = True + +[mypy-babelfish.*] +ignore_missing_imports = True + +[mypy-enzyme.*] +ignore_missing_imports = True + +[mypy-pymediainfo.*] +ignore_missing_imports = True diff --git a/setup.py b/setup.py index 56dc442..363bec6 100644 --- a/setup.py +++ b/setup.py @@ -1,80 +1,3 @@ # -*- coding: utf-8 -*- -import io -import os -import re -import sys - -from setuptools import find_packages, setup - -here = os.path.abspath(os.path.dirname(__file__)) - - -def read(*parts): - return io.open(os.path.join(here, *parts), 'r').read() - - -def find_version(*file_paths): - version_file = read(*file_paths) - version_match = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', version_file, re.M) - if version_match: - return version_match.group(1) - raise RuntimeError('Unable to find version string.') - - -setup_requirements = ['pytest-runner'] if {'pytest', 'test', 'ptr'}.intersection(sys.argv) else [] -install_requirements = [ - 'babelfish>=0.5.5;python_version<"3.10"', - 'babelfish>0.5.5;python_version>="3.10"', - 'enzyme>=0.4.1', - 'pint>=0.9', - 'pymediainfo>=3.0', - 'PyYAML>=3.13', - 'six>=1.12.0', -] -test_requirements = ['flake8_docstrings', 'flake8-import-order', 'pydocstyle', - 'pep8-naming', 'pytest>=4.3.0', 'pytest-cov', 'pytest-flake8', 'requests>=2.21.0'] - -if sys.version_info < (3, 3): - test_requirements.append('mock') - -setup( - name='knowit', - version=find_version('knowit', '__init__.py'), - description='Know better your media files', - long_description=read('README.rst'), - keywords='video mkv mp4 mediainfo metadata movie episode tv shows series', - author='Rato AQ2', - author_email='rato.aq2@gmail.com', - url='https://github.com/ratoaq2/knowit', - license='MIT', - entry_points={ - 'console_scripts': [ - 'knowit = knowit.__main__:main' - ]}, - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Multimedia :: Video' - ], - packages=find_packages(exclude=('tests', 'docs')), - include_package_data=True, - setup_requires=setup_requirements, - install_requires=install_requirements, - tests_require=test_requirements, - extras_require={ - 'test': test_requirements, - }, -) +import setuptools +setuptools.setup() diff --git a/tox.ini b/tox.ini index 2281744..d99cb88 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,30 @@ [tox] -envlist = {py27,py33,py34,py35,py36,py37,py38,py39}-{native} +envlist = py{36,37,38,39}-{native}, lint, type-check [testenv] commands = - python setup.py test --addopts "--cov-report term --cov-report html --cov knowit --verbose {posargs}" + python --version -[flake8] -import-order-style = cryptography -application-import-names = knowit \ No newline at end of file +[testenv:py{36,37,38,39}-{native}] +deps = + pytest >= 4.3.0 + pytest-cov + requests >= 2.21.0 +commands = + pytest tests --cov-report term --cov-report html --cov knowit --verbose {posargs} + +[testenv:lint] +deps = + flake8 + flake8-docstrings + flake8-import-order + pep8-naming + pydocstyle +commands = + flake8 knowit + +[testenv:type-check] +deps = + mypy +commands = + mypy knowit From b820d46cf49a9ffb3795ead2b0c6363385dbe48c Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 01:38:27 -0400 Subject: [PATCH 002/102] Fix List or tuple literal expected as the second argument to namedtuple --- knowit/config.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/knowit/config.py b/knowit/config.py index 04e8713..759c98b 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from collections import namedtuple +import typing from logging import NullHandler, getLogger from pkg_resources import resource_stream @@ -13,8 +13,15 @@ logger = getLogger(__name__) logger.addHandler(NullHandler()) -_valid_aliases = ('code', 'default', 'human', 'technical') -_Value = namedtuple('_Value', _valid_aliases) + +class _Value(typing.NamedTuple): + code: str + default: str + human: str + technical: str + + +_valid_aliases = _Value._fields class Config(object): From 4d34a4bb411debdc1b3730a2a4cc0ac2b381acda Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 01:45:52 -0400 Subject: [PATCH 003/102] Fix mypy needs type annotation for 'available_providers' --- knowit/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/knowit/api.py b/knowit/api.py index 769ec16..a7204ea 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -2,9 +2,11 @@ from __future__ import unicode_literals import traceback +import typing from . import OrderedDict, __version__ from .config import Config +from .provider import Provider from .providers import ( EnzymeProvider, FFmpegProvider, @@ -19,7 +21,7 @@ provider_names = _provider_map.keys() -available_providers = OrderedDict([]) +available_providers: typing.Dict[str, Provider] = OrderedDict([]) class KnowitException(Exception): From 433dc54090cc55fa29e2508b1456bf30bf445c7e Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 01:15:06 -0500 Subject: [PATCH 004/102] Use dict instead of OrderedDict Fixes mypy: - Cannot find implementation or library stub for module named 'ordereddict' - Name 'OrderedDict' already defined (possibly by an import) - Cannot assign to a type - Incompatible types in assignment (expression has type "Type[Dict[Any, Any]]", variable has type "Type[OrderedDict[Any, Any]]") --- knowit/__init__.py | 9 -- knowit/api.py | 16 ++-- knowit/provider.py | 3 +- knowit/providers/enzyme.py | 107 +++++++++++---------- knowit/providers/ffmpeg.py | 143 ++++++++++++++-------------- knowit/providers/mediainfo.py | 170 +++++++++++++++++----------------- knowit/serializer.py | 6 -- knowit/utils.py | 3 +- 8 files changed, 216 insertions(+), 241 deletions(-) diff --git a/knowit/__init__.py b/knowit/__init__.py index 4130edb..32be7cd 100644 --- a/knowit/__init__.py +++ b/knowit/__init__.py @@ -19,13 +19,4 @@ '.omf', '.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', '.vivo', '.vob', '.vro', '.webm', '.wm', '.wmv', '.wmx', '.wrap', '.wvx', '.wx', '.x264', '.xvid') -try: - from collections import OrderedDict -except ImportError: # pragma: no cover - from ordereddict import OrderedDict -else: - import sys - if sys.version_info >= (3, 6): - OrderedDict = dict - from .api import KnowitException, know diff --git a/knowit/api.py b/knowit/api.py index a7204ea..4cfec02 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -4,7 +4,7 @@ import traceback import typing -from . import OrderedDict, __version__ +from . import __version__ from .config import Config from .provider import Provider from .providers import ( @@ -13,15 +13,15 @@ MediaInfoProvider, ) -_provider_map = OrderedDict([ - ('mediainfo', MediaInfoProvider), - ('ffmpeg', FFmpegProvider), - ('enzyme', EnzymeProvider) -]) +_provider_map = { + 'mediainfo': MediaInfoProvider, + 'ffmpeg': FFmpegProvider, + 'enzyme': EnzymeProvider, +} provider_names = _provider_map.keys() -available_providers: typing.Dict[str, Provider] = OrderedDict([]) +available_providers: typing.Dict[str, Provider] = {} class KnowitException(Exception): @@ -74,7 +74,7 @@ def know(video_path, context=None): def dependencies(context=None): """Return all dependencies detected by knowit.""" - deps = OrderedDict([]) + deps = {} try: initialize(context) for name, provider_cls in _provider_map.items(): diff --git a/knowit/provider.py b/knowit/provider.py index cb58c01..24b16e3 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -4,7 +4,6 @@ import os from logging import NullHandler, getLogger -from . import OrderedDict from .properties import Quantity from .units import units @@ -82,7 +81,7 @@ def _describe_track(self, track, track_type, context): :param track_type: :rtype: dict """ - props = OrderedDict() + props = {} pv_props = {} for name, prop in self.mapping[track_type].items(): if not prop: diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index dd9c294..9a6153e 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -7,7 +7,6 @@ from logging import NullHandler, getLogger import enzyme -from .. import OrderedDict from ..properties import ( AudioCodec, Basic, @@ -43,60 +42,60 @@ class EnzymeProvider(Provider): def __init__(self, config, *args, **kwargs): """Init method.""" super(EnzymeProvider, self).__init__(config, { - 'general': OrderedDict([ - ('title', Property('title', description='media title')), - ('duration', Duration('duration', description='media duration')), - ]), - 'video': OrderedDict([ - ('id', Basic('number', int, description='video track number')), - ('name', Property('name', description='video track name')), - ('language', Language('language', description='video language')), - ('width', Quantity('width', units.pixel)), - ('height', Quantity('height', units.pixel)), - ('scan_type', YesNo('interlaced', yes='Interlaced', no='Progressive', default='Progressive', - description='video scan type')), - ('resolution', None), # populated with ResolutionRule - # ('bit_depth', Property('bit_depth', Integer('video bit depth'))), - ('codec', VideoCodec(config, 'codec_id', description='video codec')), - ('forced', YesNo('forced', hide_value=False, description='video track forced')), - ('default', YesNo('default', hide_value=False, description='video track default')), - ('enabled', YesNo('enabled', hide_value=True, description='video track enabled')), - ]), - 'audio': OrderedDict([ - ('id', Basic('number', int, description='audio track number')), - ('name', Property('name', description='audio track name')), - ('language', Language('language', description='audio language')), - ('codec', AudioCodec(config, 'codec_id', description='audio codec')), - ('channels_count', Basic('channels', int, description='audio channels count')), - ('channels', None), # populated with AudioChannelsRule - ('forced', YesNo('forced', hide_value=False, description='audio track forced')), - ('default', YesNo('default', hide_value=False, description='audio track default')), - ('enabled', YesNo('enabled', hide_value=True, description='audio track enabled')), - ]), - 'subtitle': OrderedDict([ - ('id', Basic('number', int, description='subtitle track number')), - ('name', Property('name', description='subtitle track name')), - ('language', Language('language', description='subtitle language')), - ('hearing_impaired', None), # populated with HearingImpairedRule - ('closed_caption', None), # populated with ClosedCaptionRule - ('forced', YesNo('forced', hide_value=False, description='subtitle track forced')), - ('default', YesNo('default', hide_value=False, description='subtitle track default')), - ('enabled', YesNo('enabled', hide_value=True, description='subtitle track enabled')), - ]), + 'general': { + 'title': Property('title', description='media title'), + 'duration': Duration('duration', description='media duration'), + }, + 'video': { + 'id': Basic('number', int, description='video track number'), + 'name': Property('name', description='video track name'), + 'language': Language('language', description='video language'), + 'width': Quantity('width', units.pixel), + 'height': Quantity('height', units.pixel), + 'scan_type': YesNo('interlaced', yes='Interlaced', no='Progressive', default='Progressive', + description='video scan type'), + 'resolution': None, # populated with ResolutionRule + # 'bit_depth', Property('bit_depth', Integer('video bit depth')), + 'codec': VideoCodec(config, 'codec_id', description='video codec'), + 'forced': YesNo('forced', hide_value=False, description='video track forced'), + 'default': YesNo('default', hide_value=False, description='video track default'), + 'enabled': YesNo('enabled', hide_value=True, description='video track enabled'), + }, + 'audio': { + 'id': Basic('number', int, description='audio track number'), + 'name': Property('name', description='audio track name'), + 'language': Language('language', description='audio language'), + 'codec': AudioCodec(config, 'codec_id', description='audio codec'), + 'channels_count': Basic('channels', int, description='audio channels count'), + 'channels': None, # populated with AudioChannelsRule + 'forced': YesNo('forced', hide_value=False, description='audio track forced'), + 'default': YesNo('default', hide_value=False, description='audio track default'), + 'enabled': YesNo('enabled', hide_value=True, description='audio track enabled'), + }, + 'subtitle': { + 'id': Basic('number', int, description='subtitle track number'), + 'name': Property('name', description='subtitle track name'), + 'language': Language('language', description='subtitle language'), + 'hearing_impaired': None, # populated with HearingImpairedRule + 'closed_caption': None, # populated with ClosedCaptionRule + 'forced': YesNo('forced', hide_value=False, description='subtitle track forced'), + 'default': YesNo('default', hide_value=False, description='subtitle track default'), + 'enabled': YesNo('enabled', hide_value=True, description='subtitle track enabled'), + }, }, { - 'video': OrderedDict([ - ('language', LanguageRule('video language')), - ('resolution', ResolutionRule('video resolution')), - ]), - 'audio': OrderedDict([ - ('language', LanguageRule('audio language')), - ('channels', AudioChannelsRule('audio channels')), - ]), - 'subtitle': OrderedDict([ - ('language', LanguageRule('subtitle language')), - ('hearing_impaired', HearingImpairedRule('subtitle hearing impaired')), - ('closed_caption', ClosedCaptionRule('closed caption')), - ]) + 'video': { + 'language': LanguageRule('video language'), + 'resolution': ResolutionRule('video resolution'), + }, + 'audio': { + 'language': LanguageRule('audio language'), + 'channels': AudioChannelsRule('audio channels'), + }, + 'subtitle': { + 'language': LanguageRule('subtitle language'), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), + 'closed_caption': ClosedCaptionRule('closed caption'), + } }) def accepts(self, video_path): diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index cc50533..29028b7 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -9,10 +9,7 @@ from six import ensure_text -from .. import ( - OrderedDict, - VIDEO_EXTENSIONS, -) +from .. import VIDEO_EXTENSIONS from ..properties import ( AudioChannels, AudioCodec, @@ -143,77 +140,77 @@ class FFmpegProvider(Provider): def __init__(self, config, suggested_path=None): """Init method.""" super(FFmpegProvider, self).__init__(config, { - 'general': OrderedDict([ - ('title', Property('tags.title', description='media title')), - ('path', Property('filename', description='media path')), - ('duration', Duration('duration', description='media duration')), - ('size', Quantity('size', units.byte, description='media size')), - ('bit_rate', Quantity('bit_rate', units.bps, description='media bit rate')), - ]), - 'video': OrderedDict([ - ('id', Basic('index', 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')), - ('width', Quantity('width', units.pixel)), - ('height', Quantity('height', units.pixel)), - ('scan_type', ScanType(config, 'field_order', default='Progressive', description='video scan type')), - ('aspect_ratio', Ratio('display_aspect_ratio', description='display aspect ratio')), - ('pixel_aspect_ratio', Ratio('sample_aspect_ratio', description='pixel aspect ratio')), - ('resolution', None), # populated with ResolutionRule - ('frame_rate', Ratio('r_frame_rate', unit=units.FPS, description='video frame rate')), + 'general': { + 'title': Property('tags.title', description='media title'), + 'path': Property('filename', description='media path'), + 'duration': Duration('duration', description='media duration'), + 'size': Quantity('size', units.byte, description='media size'), + 'bit_rate': Quantity('bit_rate', units.bps, description='media bit rate'), + }, + 'video': { + 'id': Basic('index', 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'), + 'width': Quantity('width', units.pixel), + 'height': Quantity('height', units.pixel), + 'scan_type': ScanType(config, 'field_order', default='Progressive', description='video scan type'), + 'aspect_ratio': Ratio('display_aspect_ratio', description='display aspect ratio'), + 'pixel_aspect_ratio': Ratio('sample_aspect_ratio', description='pixel aspect ratio'), + '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', units.bps, description='video bit rate')), - ('bit_depth', Quantity('bits_per_raw_sample', units.bit, description='video bit depth')), - ('codec', VideoCodec(config, 'codec_name', description='video codec')), - ('profile', VideoProfile(config, 'profile', description='video codec profile')), - ('profile_level', VideoProfileLevel(config, 'level', description='video codec profile level')), - # ('profile_tier', VideoProfileTier(config, 'codec_profile', description='video codec profile tier')), - ('forced', YesNo('disposition.forced', hide_value=False, description='video track forced')), - ('default', YesNo('disposition.default', hide_value=False, description='video track default')), - ]), - 'audio': OrderedDict([ - ('id', Basic('index', 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')), - ('codec', AudioCodec(config, 'codec_name', description='audio codec')), - ('_codec', AudioCodec(config, 'profile', description='audio codec', private=True, reportable=False)), - ('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', units.bit, description='audio bit depth')), - ('bit_rate', Quantity('bit_rate', units.bps, description='audio bit rate')), - ('sampling_rate', Quantity('sample_rate', 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')), - ]), - 'subtitle': OrderedDict([ - ('id', Basic('index', int, allow_fallback=True, description='subtitle track number')), - ('name', Property('tags.title', description='subtitle track name')), - ('language', Language('tags.language', description='subtitle language')), - ('hearing_impaired', YesNo('disposition.hearing_impaired', - hide_value=False, description='subtitle hearing impaired')), - ('closed_caption', None), # populated with ClosedCaptionRule - ('format', SubtitleFormat(config, 'codec_name', description='subtitle format')), - ('forced', YesNo('disposition.forced', hide_value=False, description='subtitle track forced')), - ('default', YesNo('disposition.default', hide_value=False, description='subtitle track default')), - ]), + 'bit_rate': Quantity('bit_rate', units.bps, description='video bit rate'), + 'bit_depth': Quantity('bits_per_raw_sample', units.bit, description='video bit depth'), + 'codec': VideoCodec(config, 'codec_name', description='video codec'), + 'profile': VideoProfile(config, 'profile', description='video codec profile'), + 'profile_level': VideoProfileLevel(config, 'level', description='video codec profile level'), + # 'profile_tier': VideoProfileTier(config, 'codec_profile', description='video codec profile tier'), + 'forced': YesNo('disposition.forced', hide_value=False, description='video track forced'), + 'default': YesNo('disposition.default', hide_value=False, description='video track default'), + }, + 'audio': { + 'id': Basic('index', 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'), + 'codec': AudioCodec(config, 'codec_name', description='audio codec'), + '_codec': AudioCodec(config, 'profile', description='audio codec', private=True, reportable=False), + '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', units.bit, description='audio bit depth'), + 'bit_rate': Quantity('bit_rate', units.bps, description='audio bit rate'), + 'sampling_rate': Quantity('sample_rate', 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'), + }, + 'subtitle': { + 'id': Basic('index', int, allow_fallback=True, description='subtitle track number'), + 'name': Property('tags.title', description='subtitle track name'), + 'language': Language('tags.language', description='subtitle language'), + 'hearing_impaired': YesNo('disposition.hearing_impaired', + hide_value=False, description='subtitle hearing impaired'), + 'closed_caption': None, # populated with ClosedCaptionRule + 'format': SubtitleFormat(config, 'codec_name', description='subtitle format'), + 'forced': YesNo('disposition.forced', hide_value=False, description='subtitle track forced'), + 'default': YesNo('disposition.default', hide_value=False, description='subtitle track default'), + }, }, { - 'video': OrderedDict([ - ('language', LanguageRule('video language')), - ('resolution', ResolutionRule('video resolution')), - ]), - 'audio': OrderedDict([ - ('language', LanguageRule('audio language')), - ('channels', AudioChannelsRule('audio channels')), - ('codec', AudioCodecRule('audio codec', override=True)), - ]), - 'subtitle': OrderedDict([ - ('language', LanguageRule('subtitle language')), - ('hearing_impaired', HearingImpairedRule('subtitle hearing impaired')), - ('closed_caption', ClosedCaptionRule('closed caption')) - ]) + 'video': { + 'language': LanguageRule('video language'), + 'resolution': ResolutionRule('video resolution'), + }, + 'audio': { + 'language': LanguageRule('audio language'), + 'channels': AudioChannelsRule('audio channels'), + 'codec': AudioCodecRule('audio codec', override=True), + }, + 'subtitle': { + 'language': LanguageRule('subtitle language'), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), + 'closed_caption': ClosedCaptionRule('closed caption'), + }, }) self.executor = FFmpegExecutor.get_executor_instance(suggested_path) diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 3843e0d..a4f9329 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -12,10 +12,7 @@ from pymediainfo import __version__ as pymediainfo_version from six import ensure_text -from .. import ( - OrderedDict, - VIDEO_EXTENSIONS, -) +from .. import VIDEO_EXTENSIONS from ..properties import ( AudioChannels, AudioCodec, @@ -188,87 +185,87 @@ class MediaInfoProvider(Provider): def __init__(self, config, suggested_path): """Init method.""" super(MediaInfoProvider, self).__init__(config, { - 'general': OrderedDict([ - ('title', Property('title', description='media title')), - ('path', Property('complete_name', description='media path')), - ('duration', Duration('duration', description='media duration')), - ('size', Quantity('file_size', units.byte, description='media size')), - ('bit_rate', Quantity('overall_bit_rate', units.bps, description='media bit rate')), - ]), - 'video': OrderedDict([ - ('id', Basic('track_id', int, allow_fallback=True, description='video track number')), - ('name', Property('name', description='video track name')), - ('language', Language('language', description='video language')), - ('duration', Duration('duration', description='video duration')), - ('size', Quantity('stream_size', units.byte, description='video stream size')), - ('width', Quantity('width', units.pixel)), - ('height', Quantity('height', units.pixel)), - ('scan_type', ScanType(config, 'scan_type', default='Progressive', description='video scan type')), - ('aspect_ratio', Basic('display_aspect_ratio', float, description='display aspect ratio')), - ('pixel_aspect_ratio', Basic('pixel_aspect_ratio', float, description='pixel aspect ratio')), - ('resolution', None), # populated with ResolutionRule - ('frame_rate', Quantity('frame_rate', units.FPS, float, description='video frame rate')), + 'general': { + 'title': Property('title', description='media title'), + 'path': Property('complete_name', description='media path'), + 'duration': Duration('duration', description='media duration'), + 'size': Quantity('file_size', units.byte, description='media size'), + 'bit_rate': Quantity('overall_bit_rate', units.bps, description='media bit rate'), + }, + 'video': { + 'id': Basic('track_id', int, allow_fallback=True, description='video track number'), + 'name': Property('name', description='video track name'), + 'language': Language('language', description='video language'), + 'duration': Duration('duration', description='video duration'), + 'size': Quantity('stream_size', units.byte, description='video stream size'), + 'width': Quantity('width', units.pixel), + 'height': Quantity('height', units.pixel), + 'scan_type': ScanType(config, 'scan_type', default='Progressive', description='video scan type'), + 'aspect_ratio': Basic('display_aspect_ratio', float, description='display aspect ratio'), + 'pixel_aspect_ratio': Basic('pixel_aspect_ratio', float, description='pixel aspect ratio'), + 'resolution': None, # populated with ResolutionRule + 'frame_rate': Quantity('frame_rate', units.FPS, float, description='video frame rate'), # frame_rate_mode - ('bit_rate', Quantity('bit_rate', units.bps, description='video bit rate')), - ('bit_depth', Quantity('bit_depth', units.bit, description='video bit depth')), - ('codec', VideoCodec(config, 'codec', description='video codec')), - ('profile', VideoProfile(config, 'codec_profile', description='video codec profile')), - ('profile_level', VideoProfileLevel(config, 'codec_profile', description='video codec profile level')), - ('profile_tier', VideoProfileTier(config, 'codec_profile', description='video codec profile tier')), - ('encoder', VideoEncoder(config, 'encoded_library_name', description='video encoder')), - ('media_type', Property('internet_media_type', description='video media type')), - ('forced', YesNo('forced', hide_value=False, description='video track forced')), - ('default', YesNo('default', hide_value=False, description='video track default')), - ]), - 'audio': OrderedDict([ - ('id', Basic('track_id', int, allow_fallback=True, description='audio track number')), - ('name', Property('title', description='audio track name')), - ('language', Language('language', description='audio language')), - ('duration', Duration('duration', description='audio duration')), - ('size', Quantity('stream_size', units.byte, description='audio stream size')), - ('codec', MultiValue(AudioCodec(config, 'codec', description='audio codec'))), - ('profile', MultiValue(AudioProfile(config, 'format_profile', description='audio codec profile'), - delimiter=' / ')), - ('channels_count', MultiValue(AudioChannels('channel_s', description='audio channels count'))), - ('channel_positions', MultiValue(name='other_channel_positions', handler=(lambda x, *args: x), - delimiter=' / ', private=True, description='audio channels position')), - ('channels', None), # populated with AudioChannelsRule - ('bit_depth', Quantity('bit_depth', units.bit, description='audio bit depth')), - ('bit_rate', MultiValue(Quantity('bit_rate', units.bps, description='audio bit rate'))), - ('bit_rate_mode', MultiValue(BitRateMode(config, 'bit_rate_mode', description='audio bit rate mode'))), - ('sampling_rate', MultiValue(Quantity('sampling_rate', units.Hz, description='audio sampling rate'))), - ('compression', MultiValue(AudioCompression(config, 'compression_mode', - description='audio compression'))), - ('forced', YesNo('forced', hide_value=False, description='audio track forced')), - ('default', YesNo('default', hide_value=False, description='audio track default')), - ]), - 'subtitle': OrderedDict([ - ('id', Basic('track_id', int, allow_fallback=True, description='subtitle track number')), - ('name', Property('title', description='subtitle track name')), - ('language', Language('language', description='subtitle language')), - ('hearing_impaired', None), # populated with HearingImpairedRule - ('_closed_caption', Property('captionservicename', private=True)), - ('closed_caption', None), # populated with ClosedCaptionRule - ('format', SubtitleFormat(config, 'codec_id', description='subtitle format')), - ('forced', YesNo('forced', hide_value=False, description='subtitle track forced')), - ('default', YesNo('default', hide_value=False, description='subtitle track default')), - ]), + 'bit_rate': Quantity('bit_rate', units.bps, description='video bit rate'), + 'bit_depth': Quantity('bit_depth', units.bit, description='video bit depth'), + 'codec': VideoCodec(config, 'codec', description='video codec'), + 'profile': VideoProfile(config, 'codec_profile', description='video codec profile'), + 'profile_level': VideoProfileLevel(config, 'codec_profile', description='video codec profile level'), + 'profile_tier': VideoProfileTier(config, 'codec_profile', description='video codec profile tier'), + 'encoder': VideoEncoder(config, 'encoded_library_name', description='video encoder'), + 'media_type': Property('internet_media_type', description='video media type'), + 'forced': YesNo('forced', hide_value=False, description='video track forced'), + 'default': YesNo('default', hide_value=False, description='video track default'), + }, + 'audio': { + 'id': Basic('track_id', int, allow_fallback=True, description='audio track number'), + 'name': Property('title', description='audio track name'), + 'language': Language('language', description='audio language'), + 'duration': Duration('duration', description='audio duration'), + 'size': Quantity('stream_size', units.byte, description='audio stream size'), + 'codec': MultiValue(AudioCodec(config, 'codec', description='audio codec')), + 'profile': MultiValue(AudioProfile(config, 'format_profile', description='audio codec profile'), + delimiter=' / '), + 'channels_count': MultiValue(AudioChannels('channel_s', description='audio channels count')), + 'channel_positions': MultiValue(name='other_channel_positions', handler=(lambda x, *args: x), + delimiter=' / ', private=True, description='audio channels position'), + 'channels': None, # populated with AudioChannelsRule + 'bit_depth': Quantity('bit_depth', units.bit, description='audio bit depth'), + 'bit_rate': MultiValue(Quantity('bit_rate', units.bps, description='audio bit rate')), + 'bit_rate_mode': MultiValue(BitRateMode(config, 'bit_rate_mode', description='audio bit rate mode')), + 'sampling_rate': MultiValue(Quantity('sampling_rate', units.Hz, description='audio sampling rate')), + 'compression': MultiValue(AudioCompression(config, 'compression_mode', + description='audio compression')), + 'forced': YesNo('forced', hide_value=False, description='audio track forced'), + 'default': YesNo('default', hide_value=False, description='audio track default'), + }, + 'subtitle': { + 'id': Basic('track_id', int, allow_fallback=True, description='subtitle track number'), + 'name': Property('title', description='subtitle track name'), + 'language': Language('language', description='subtitle language'), + 'hearing_impaired': None, # populated with HearingImpairedRule + '_closed_caption': Property('captionservicename', private=True), + 'closed_caption': None, # populated with ClosedCaptionRule + 'format': SubtitleFormat(config, 'codec_id', description='subtitle format'), + 'forced': YesNo('forced', hide_value=False, description='subtitle track forced'), + 'default': YesNo('default', hide_value=False, description='subtitle track default'), + }, }, { - 'video': OrderedDict([ - ('language', LanguageRule('video language')), - ('resolution', ResolutionRule('video resolution')), - ]), - 'audio': OrderedDict([ - ('language', LanguageRule('audio language')), - ('channels', AudioChannelsRule('audio channels')), - ('_atmosrule', AtmosRule('atmos rule')), - ('_dtshdrule', DtsHdRule('dts-hd rule')), - ]), - 'subtitle': OrderedDict([ - ('language', LanguageRule('subtitle language')), - ('hearing_impaired', HearingImpairedRule('subtitle hearing impaired')), - ('closed_caption', ClosedCaptionRule('closed caption')), - ]) + 'video': { + 'language': LanguageRule('video language'), + 'resolution': ResolutionRule('video resolution'), + }, + 'audio': { + 'language': LanguageRule('audio language'), + 'channels': AudioChannelsRule('audio channels'), + '_atmosrule': AtmosRule('atmos rule'), + '_dtshdrule': DtsHdRule('dts-hd rule'), + }, + 'subtitle': { + 'language': LanguageRule('subtitle language'), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), + 'closed_caption': ClosedCaptionRule('closed caption'), + } }) self.executor = MediaInfoExecutor.get_executor_instance(suggested_path) @@ -328,8 +325,7 @@ def debug_data(): @property def version(self): """Return mediainfo version information.""" - versions = [('pymediainfo', pymediainfo_version)] + versions = {'pymediainfo': pymediainfo_version} if self.executor: - versions.append((self.executor.location, 'v{}'.format('.'.join(map(str, self.executor.version))))) - - return OrderedDict(versions) + versions[self.executor.location] = 'v{}'.format('.'.join(map(str, self.executor.version))) + return versions diff --git a/knowit/serializer.py b/knowit/serializer.py index a799df7..dc8fab7 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals import json -from collections import OrderedDict from datetime import timedelta import babelfish @@ -50,10 +49,6 @@ def default_representer(self, data): return self.represent_float(data) return self.represent_str(str(data)) - def ordered_dict_representer(self, data): - """Representer for OrderedDict.""" - return self.represent_mapping('tag:yaml.org,2002:map', data.items()) - def default_language_representer(self, data): """Convert language to string.""" return self.represent_str(format_language(data, context['profile'])) @@ -66,7 +61,6 @@ def default_duration_representer(self, data): """Convert quantity to string.""" return self.default_representer(format_duration(data, context['profile'])) - CustomDumper.add_representer(OrderedDict, CustomDumper.ordered_dict_representer) CustomDumper.add_representer(babelfish.Language, CustomDumper.default_language_representer) CustomDumper.add_representer(timedelta, CustomDumper.default_duration_representer) CustomDumper.add_representer(units.Quantity, CustomDumper.default_quantity_representer) diff --git a/knowit/utils.py b/knowit/utils.py index 6898243..de37be1 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -3,7 +3,6 @@ import os import sys -from collections import OrderedDict from six import PY2, string_types, text_type @@ -59,7 +58,7 @@ def todict(obj, classkey=None): elif hasattr(obj, '__dict__'): values = [(key, todict(value, classkey)) for key, value in obj.__dict__.items() if not callable(value) and not key.startswith('_')] - data = OrderedDict([(k, v) for k, v in values if v is not None]) + data = {k: v for k, v in values if v is not None} if classkey is not None and hasattr(obj, '__class__'): data[classkey] = obj.__class__.__name__ return data From 6e82b8ed9d2a777a891dffa35e4c200802b74a30 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 01:18:22 -0500 Subject: [PATCH 005/102] Fix deriving from object All class are new-style classes in Python 3 --- knowit/config.py | 2 +- knowit/core.py | 2 +- knowit/provider.py | 2 +- knowit/providers/ffmpeg.py | 2 +- knowit/providers/mediainfo.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/knowit/config.py b/knowit/config.py index 759c98b..a994c43 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -24,7 +24,7 @@ class _Value(typing.NamedTuple): _valid_aliases = _Value._fields -class Config(object): +class Config: """Application config class.""" @classmethod diff --git a/knowit/core.py b/knowit/core.py index f1fbcd9..1144ecf 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -9,7 +9,7 @@ logger.addHandler(NullHandler()) -class Reportable(object): +class Reportable: """Reportable abstract class.""" def __init__(self, name, description=None, reportable=True): diff --git a/knowit/provider.py b/knowit/provider.py index 24b16e3..bd006f1 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -14,7 +14,7 @@ size_property = Quantity('size', units.byte, description='media size') -class Provider(object): +class Provider: """Base class for all providers.""" min_fps = 10 diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index 29028b7..71d393c 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -66,7 +66,7 @@ ''' -class FFmpegExecutor(object): +class FFmpegExecutor: """Executor that knows how to execute media info: using ctypes or cli.""" version_re = re.compile(r'\bversion\s+(?P[^\b\s]+)') diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index a4f9329..641dc4a 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -76,7 +76,7 @@ ''' -class MediaInfoExecutor(object): +class MediaInfoExecutor: """Media info executable knows how to execute media info: using ctypes or cli.""" version_re = re.compile(r'\bv(?P\d+(?:\.\d+)+)\b') From f0ced466ca1562d5bbb82bd721c0ba07e6fd29e4 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 01:54:43 -0500 Subject: [PATCH 006/102] Remove Python Version checks for end-of-life versions # Conflicts: # setup.py --- knowit/__main__.py | 4 ---- knowit/property.py | 5 ++--- knowit/utils.py | 9 +++------ 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/knowit/__main__.py b/knowit/__main__.py index 3b55af8..310b837 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -6,7 +6,6 @@ import sys from argparse import ArgumentParser -from six import PY2 import yaml from . import ( @@ -88,9 +87,6 @@ def dump(info, options, context): data = {info['path']: info} if 'path' in info else info result = yaml.dump(data, Dumper=get_yaml_dumper(context), default_flow_style=False, allow_unicode=True) - if PY2: - result = result.decode('utf-8') - else: result = json.dumps(info, cls=get_json_encoder(context), indent=4, ensure_ascii=False) diff --git a/knowit/property.py b/knowit/property.py index 475ea40..516aa8f 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from logging import NullHandler, getLogger -from six import PY3, binary_type, string_types, text_type +from six import binary_type, string_types, text_type from .core import Reportable @@ -133,5 +133,4 @@ def _split(cls, value, delimiter='/'): return v = text_type(value) - result = map(text_type.strip, v.split(delimiter)) - return list(result) if PY3 else result + return [x.strip() for x in v.split(delimiter)] diff --git a/knowit/utils.py b/knowit/utils.py index de37be1..52f9c25 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -4,7 +4,7 @@ import os import sys -from six import PY2, string_types, text_type +from six import string_types, text_type from . import VIDEO_EXTENSIONS @@ -25,15 +25,12 @@ def recurse_paths(paths): encoding = sys.getfilesystemencoding() for path in paths: if os.path.isfile(path): - enc_paths.append(path.decode(encoding) if PY2 else path) + enc_paths.append(path) if os.path.isdir(path): for root, directories, filenames in os.walk(path): for filename in filenames: if os.path.splitext(filename)[1] in VIDEO_EXTENSIONS: - if PY2 and os.name == 'nt': - fullpath = os.path.join(root, filename.decode(encoding)) - else: - fullpath = os.path.join(root, filename).decode(encoding) + fullpath = os.path.join(root, filename).decode(encoding) enc_paths.append(fullpath) # Lets remove any dupes since mediainfo is rather slow. From bdeb9ef98bb15c11781e9250080949f1ed844161 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 02:01:58 -0500 Subject: [PATCH 007/102] User Python 3 super calls --- knowit/properties/basic.py | 2 +- knowit/properties/quantity.py | 2 +- knowit/properties/video/ratio.py | 2 +- knowit/properties/yesno.py | 2 +- knowit/property.py | 6 +++--- knowit/providers/enzyme.py | 2 +- knowit/providers/ffmpeg.py | 2 +- knowit/providers/mediainfo.py | 2 +- knowit/rule.py | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index 46176cd..04c5276 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -11,7 +11,7 @@ class Basic(Property): def __init__(self, name, data_type, allow_fallback=False, **kwargs): """Init method.""" - super(Basic, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.data_type = data_type self.allow_fallback = allow_fallback diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index 487dc27..b64c9aa 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -11,7 +11,7 @@ class Quantity(Property): def __init__(self, name, unit, data_type=int, **kwargs): """Init method.""" - super(Quantity, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.unit = unit self.data_type = data_type diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index de4e416..f0d7581 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -13,7 +13,7 @@ class Ratio(Property): def __init__(self, name, unit=None, **kwargs): """Initialize the object.""" - super(Ratio, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.unit = unit ratio_re = re.compile(r'(?P\d+)[:/](?P\d+)') diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index 28edce5..f587f89 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -13,7 +13,7 @@ class YesNo(Property): def __init__(self, name, yes=True, no=False, hide_value=None, **kwargs): """Init method.""" - super(YesNo, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.yes = yes self.no = no self.hide_value = hide_value diff --git a/knowit/property.py b/knowit/property.py index 516aa8f..dff8d67 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -21,7 +21,7 @@ class Property(Reportable): def __init__(self, name, default=None, private=False, description=None, delimiter=' / ', **kwargs): """Init method.""" - super(Property, self).__init__(name, description, **kwargs) + super().__init__(name, description, **kwargs) self.default = default self.private = private # Used to detect duplicated values. e.g.: en / en or High@L4.0 / High@L4.0 or Progressive / Progressive @@ -67,7 +67,7 @@ class Configurable(Property): def __init__(self, config, *args, **kwargs): """Init method.""" - super(Configurable, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.mapping = getattr(config, self.__class__.__name__) @classmethod @@ -111,7 +111,7 @@ class MultiValue(Property): def __init__(self, prop=None, delimiter='/', single=False, handler=None, name=None, **kwargs): """Init method.""" - super(MultiValue, self).__init__(prop.name if prop else name, **kwargs) + super().__init__(prop.name if prop else name, **kwargs) self.prop = prop self.delimiter = delimiter self.single = single diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index 9a6153e..bd88a2d 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -41,7 +41,7 @@ class EnzymeProvider(Provider): def __init__(self, config, *args, **kwargs): """Init method.""" - super(EnzymeProvider, self).__init__(config, { + super().__init__(config, { 'general': { 'title': Property('title', description='media title'), 'duration': Duration('duration', description='media duration'), diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index 71d393c..fb8ef6c 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -139,7 +139,7 @@ class FFmpegProvider(Provider): def __init__(self, config, suggested_path=None): """Init method.""" - super(FFmpegProvider, self).__init__(config, { + super().__init__(config, { 'general': { 'title': Property('tags.title', description='media title'), 'path': Property('filename', description='media path'), diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 641dc4a..f8853c1 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -184,7 +184,7 @@ class MediaInfoProvider(Provider): def __init__(self, config, suggested_path): """Init method.""" - super(MediaInfoProvider, self).__init__(config, { + super().__init__(config, { 'general': { 'title': Property('title', description='media title'), 'path': Property('complete_name', description='media path'), diff --git a/knowit/rule.py b/knowit/rule.py index a6c1083..5365199 100644 --- a/knowit/rule.py +++ b/knowit/rule.py @@ -9,7 +9,7 @@ class Rule(Reportable): def __init__(self, name, override=False, **kwargs): """Initialize the object.""" - super(Rule, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.override = override def execute(self, props, pv_props, context): From b0db2cd89296b563d417e0bc5f0908d26e49a475 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 02:49:53 -0500 Subject: [PATCH 008/102] Remove Six as a dependency --- knowit/config.py | 3 +-- knowit/core.py | 3 --- knowit/properties/audio/channels.py | 4 +--- knowit/properties/audio/codec.py | 4 +--- knowit/properties/basic.py | 4 +--- knowit/properties/duration.py | 4 +--- knowit/properties/quantity.py | 4 +--- knowit/properties/subtitle/format.py | 4 +--- knowit/properties/video/profile.py | 6 ++---- knowit/properties/video/ratio.py | 4 +--- knowit/properties/yesno.py | 5 +---- knowit/property.py | 22 ++++++++++------------ knowit/providers/ffmpeg.py | 8 +++----- knowit/providers/mediainfo.py | 11 +++++------ knowit/rules/audio/channels.py | 5 ++--- knowit/serializer.py | 3 +-- knowit/utils.py | 6 ++---- tests/__init__.py | 6 +++--- 18 files changed, 37 insertions(+), 69 deletions(-) diff --git a/knowit/config.py b/knowit/config.py index a994c43..f858ff6 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -5,7 +5,6 @@ from logging import NullHandler, getLogger from pkg_resources import resource_stream -from six import text_type import yaml from .serializer import get_yaml_loader @@ -59,7 +58,7 @@ def build(cls, path=None): alias_map.setdefault('technical', alias_map['human']) value = _Value(**{k: v for k, v in alias_map.items() if k in _valid_aliases}) for detection_value in detection_values: - data[class_name][text_type(detection_value)] = value + data[class_name][str(detection_value)] = value config = Config() config.__dict__ = data diff --git a/knowit/core.py b/knowit/core.py index 1144ecf..cf8804b 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -3,8 +3,6 @@ from logging import NullHandler, getLogger -from six import text_type - logger = getLogger(__name__) logger.addHandler(NullHandler()) @@ -28,7 +26,6 @@ def report(self, value, context): if not value or not self.reportable: return - value = text_type(value) if 'report' in context: report_map = context['report'].setdefault(self.description, {}) if value not in report_map: diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py index 597a46b..b1474bb 100644 --- a/knowit/properties/audio/channels.py +++ b/knowit/properties/audio/channels.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from six import text_type - from ...property import Property @@ -18,7 +16,7 @@ def handle(self, value, context): if isinstance(value, int): return value - v = text_type(value).lower() + v = value.lower() if v not in self.ignored: try: return int(v) diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index 9107de4..b527286 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from six import text_type - from ...property import Configurable @@ -11,7 +9,7 @@ class AudioCodec(Configurable): @classmethod def _extract_key(cls, value): - key = text_type(value).upper() + key = str(value).upper() if key.startswith('A_'): key = key[2:] diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index 04c5276..3ce7667 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from six import text_type - from ..property import Property @@ -21,7 +19,7 @@ def handle(self, value, context): return value try: - return self.data_type(text_type(value)) + return self.data_type(value) except ValueError: if not self.allow_fallback: self.report(value, context) diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index f902356..949820c 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -4,8 +4,6 @@ import re from datetime import timedelta -from six import text_type - from ..property import Property @@ -30,7 +28,7 @@ def handle(self, value, context): pass try: - h, m, s, ms, mc = self.duration_re.match(text_type(value)).groups('0') + h, m, s, ms, mc = self.duration_re.match(value).groups('0') return timedelta(hours=int(h), minutes=int(m), seconds=int(s), milliseconds=int(ms), microseconds=int(mc)) except ValueError: pass diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index b64c9aa..19bc6a5 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from six import text_type - from ..property import Property @@ -19,7 +17,7 @@ def handle(self, value, context): """Handle value with unit.""" if not isinstance(value, self.data_type): try: - value = self.data_type(text_type(value)) + value = self.data_type(value) except ValueError: self.report(value, context) return diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle/format.py index 7d57348..461c0c9 100644 --- a/knowit/properties/subtitle/format.py +++ b/knowit/properties/subtitle/format.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from six import text_type - from ...property import Configurable @@ -11,7 +9,7 @@ class SubtitleFormat(Configurable): @classmethod def _extract_key(cls, value): - key = text_type(value) .upper() + key = str(value).upper() if key.startswith('S_'): key = key[2:] diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py index 2459d40..1ddbae9 100644 --- a/knowit/properties/video/profile.py +++ b/knowit/properties/video/profile.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from six import text_type - from ...property import Configurable @@ -19,7 +17,7 @@ class VideoProfileLevel(Configurable): @classmethod def _extract_key(cls, value): - values = text_type(value).upper().split('@') + values = str(value).upper().split('@') if len(values) > 1: value = values[1] return value @@ -33,7 +31,7 @@ class VideoProfileTier(Configurable): @classmethod def _extract_key(cls, value): - values = value.upper().split('@') + values = str(value).upper().split('@') if len(values) > 2: return values[2] diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index f0d7581..6be1891 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -3,8 +3,6 @@ import re -from six import text_type - from ...property import Property @@ -20,7 +18,7 @@ def __init__(self, name, unit=None, **kwargs): def handle(self, value, context): """Handle ratio.""" - match = self.ratio_re.match(text_type(value)) + match = self.ratio_re.match(value) if match: width, height = match.groups() if (width, height) == ('0', '1'): # identity diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index f587f89..6866e2b 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from six import text_type - from ..property import Property @@ -20,6 +18,5 @@ def __init__(self, name, yes=True, no=False, hide_value=None, **kwargs): def handle(self, value, context): """Handle boolean values.""" - v = text_type(value).lower() - result = self.yes if v in self.mapping else self.no + result = self.yes if str(value).lower() in self.mapping else self.no return result if result != self.hide_value else None diff --git a/knowit/property.py b/knowit/property.py index dff8d67..52e01fe 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from logging import NullHandler, getLogger -from six import binary_type, string_types, text_type from .core import Reportable @@ -13,7 +12,7 @@ def _is_unknown(value): - return isinstance(value, text_type) and (not value or value.lower() == 'unknown') + return isinstance(value, str) and (not value or value.lower() == 'unknown') class Property(Reportable): @@ -37,13 +36,13 @@ def extract_value(self, track, context): value = self.default - if isinstance(value, string_types): - if isinstance(value, binary_type): - value = text_type(value) - else: - value = value.translate(_visible_chars_table).strip() - if _is_unknown(value): - return + if isinstance(value, bytes): + value = value.decode() + + if isinstance(value, str): + value = value.translate(_visible_chars_table).strip() + if _is_unknown(value): + return value = self._deduplicate(value) result = self.handle(value, context) @@ -72,7 +71,7 @@ def __init__(self, config, *args, **kwargs): @classmethod def _extract_key(cls, value): - return text_type(value).upper() + return value.upper() @classmethod def _extract_fallback_key(cls, value, key): @@ -132,5 +131,4 @@ def _split(cls, value, delimiter='/'): if value is None: return - v = text_type(value) - return [x.strip() for x in v.split(delimiter)] + return [x.strip() for x in str(value).split(delimiter)] diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index fb8ef6c..bf7b6d2 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -7,8 +7,6 @@ from logging import NullHandler, getLogger from subprocess import check_output -from six import ensure_text - from .. import VIDEO_EXTENSIONS from ..properties import ( AudioChannels, @@ -117,15 +115,15 @@ class FFmpegCliExecutor(FFmpegExecutor): } def _execute(self, filename): - return ensure_text(check_output([self.location, '-v', 'quiet', '-print_format', 'json', - '-show_format', '-show_streams', '-sexagesimal', filename])) + return check_output([self.location, '-v', 'quiet', '-print_format', 'json', + '-show_format', '-show_streams', '-sexagesimal', filename]).decode() @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): try: - output = ensure_text(check_output([candidate, '-version'])) + output = check_output([candidate, '-version']).decode() version = cls._get_version(output) if version: logger.debug('FFmpeg cli detected: %s v%s', candidate, version) diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index f8853c1..f2c155d 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -10,7 +10,6 @@ from pymediainfo import MediaInfo from pymediainfo import __version__ as pymediainfo_version -from six import ensure_text from .. import VIDEO_EXTENSIONS from ..properties import ( @@ -128,21 +127,21 @@ class MediaInfoCliExecutor(MediaInfoExecutor): def _execute(self, filename): output_type = 'OLDXML' if self.version >= (17, 10) else 'XML' - return MediaInfo(ensure_text(check_output([self.location, '--Output=' + output_type, '--Full', filename]))) + return MediaInfo(check_output([self.location, '--Output=' + output_type, '--Full', filename]).decode()) @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): try: - output = ensure_text(check_output([candidate, '--version'])) + output = check_output([candidate, '--version']).decode() version = cls._get_version(output) if version: logger.debug('MediaInfo cli detected: %s', candidate) return MediaInfoCliExecutor(candidate, version) except CalledProcessError as e: # old mediainfo returns non-zero exit code for mediainfo --version - version = cls._get_version(ensure_text(e.output)) + version = cls._get_version(e.output) if version: logger.debug('MediaInfo cli detected: %s', candidate) return MediaInfoCliExecutor(candidate, version) @@ -283,8 +282,8 @@ def describe(self, video_path, context): def debug_data(): """Debug data.""" - xml = ensure_text(ElementTree.tostring(media_info.xml_dom)).replace('\r', '').replace('\n', '') - return ensure_text(minidom.parseString(xml).toprettyxml(indent=' ', newl='\n', encoding='utf-8')) + xml = ElementTree.tostring(media_info.xml_dom).decode().replace('\r', '').replace('\n', '') + return minidom.parseString(xml).toprettyxml(indent=' ', newl='\n', encoding='utf-8').decode() context['debug_data'] = debug_data diff --git a/knowit/rules/audio/channels.py b/knowit/rules/audio/channels.py index 50975d5..9caa7bd 100644 --- a/knowit/rules/audio/channels.py +++ b/knowit/rules/audio/channels.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from logging import NullHandler, getLogger -from six import text_type from ...rule import Rule @@ -44,7 +43,7 @@ def execute(self, props, pv_props, context): c_count = int(c) + int(round((c - int(c)) * 10)) if c_count == count: - return text_type(c) + return str(c) candidate = max(candidate, c) @@ -52,6 +51,6 @@ def execute(self, props, pv_props, context): return channels if candidate: - return text_type(candidate) + return candidate self.report(positions, context) diff --git a/knowit/serializer.py b/knowit/serializer.py index dc8fab7..e8ce363 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -5,7 +5,6 @@ from datetime import timedelta import babelfish -from six import text_type import yaml from .units import units @@ -22,7 +21,7 @@ def format_property(context, o): if hasattr(o, 'units'): return format_quantity(o, context['profile']) - return text_type(o) + return str(o) def get_json_encoder(context): diff --git a/knowit/utils.py b/knowit/utils.py index 52f9c25..414c348 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -4,8 +4,6 @@ import os import sys -from six import string_types, text_type - from . import VIDEO_EXTENSIONS @@ -19,7 +17,7 @@ def recurse_paths(paths): """ enc_paths = [] - if isinstance(paths, (string_types, text_type)): + if isinstance(paths, str): paths = [p.strip() for p in paths.split(',')] if ',' in paths else paths.split() encoding = sys.getfilesystemencoding() @@ -41,7 +39,7 @@ def recurse_paths(paths): def todict(obj, classkey=None): """Transform an object to dict.""" - if isinstance(obj, string_types): + if isinstance(obj, str): return obj elif isinstance(obj, dict): data = {} diff --git a/tests/__init__.py b/tests/__init__.py index 6838d56..dad92b2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,10 +7,10 @@ import sys from collections.abc import Mapping from datetime import timedelta +from io import BytesIO from zipfile import ZipFile import requests -from six import BytesIO, string_types import yaml from yaml.constructor import Constructor @@ -245,7 +245,7 @@ def parse_duration(value): return value def parse_quantity(value): - if isinstance(value, string_types): + if isinstance(value, str): for unit in ('pixel', 'bit', 'byte', 'FPS', 'bps', 'Hz'): if value.endswith(' ' + unit): return units(value[:-(len(unit))] + ' * ' + unit) @@ -254,7 +254,7 @@ def parse_quantity(value): result = node.value for method in (parse_quantity, parse_duration): - if result and isinstance(result, string_types): + if result and isinstance(result, str): result = method(node.value) return result From ae16ae448f284d0e0b6c1a52bd7a4e9b668a25d0 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 12:14:00 -0500 Subject: [PATCH 009/102] Remove obsolete __future__ imports --- knowit/__init__.py | 1 - knowit/__main__.py | 1 - knowit/api.py | 1 - knowit/config.py | 1 - knowit/core.py | 1 - knowit/properties/__init__.py | 1 - knowit/properties/audio/__init__.py | 1 - knowit/properties/audio/bitratemode.py | 1 - knowit/properties/audio/channels.py | 1 - knowit/properties/audio/codec.py | 1 - knowit/properties/audio/compression.py | 1 - knowit/properties/audio/profile.py | 1 - knowit/properties/basic.py | 1 - knowit/properties/duration.py | 1 - knowit/properties/language.py | 1 - knowit/properties/quantity.py | 1 - knowit/properties/subtitle/__init__.py | 1 - knowit/properties/subtitle/format.py | 1 - knowit/properties/video/__init__.py | 1 - knowit/properties/video/codec.py | 1 - knowit/properties/video/encoder.py | 1 - knowit/properties/video/profile.py | 1 - knowit/properties/video/ratio.py | 1 - knowit/properties/video/scantype.py | 1 - knowit/properties/yesno.py | 1 - knowit/property.py | 1 - knowit/provider.py | 1 - knowit/providers/__init__.py | 1 - knowit/providers/enzyme.py | 1 - knowit/providers/ffmpeg.py | 1 - knowit/providers/mediainfo.py | 1 - knowit/rule.py | 1 - knowit/rules/__init__.py | 1 - knowit/rules/audio/__init__.py | 1 - knowit/rules/audio/atmos.py | 1 - knowit/rules/audio/channels.py | 1 - knowit/rules/audio/codec.py | 1 - knowit/rules/audio/dtshd.py | 1 - knowit/rules/language.py | 1 - knowit/rules/subtitle/__init__.py | 1 - knowit/rules/subtitle/closedcaption.py | 1 - knowit/rules/subtitle/hearingimpaired.py | 1 - knowit/rules/video/__init__.py | 1 - knowit/rules/video/resolution.py | 1 - knowit/serializer.py | 1 - knowit/utils.py | 1 - tests/__init__.py | 1 - tests/conftest.py | 1 - tests/test_audiochannels.py | 1 - tests/test_enzyme.py | 1 - tests/test_ffmpeg.py | 1 - tests/test_mediainfo.py | 1 - tests/test_properties.py | 1 - tests/test_resolution.py | 1 - 54 files changed, 54 deletions(-) diff --git a/knowit/__init__.py b/knowit/__init__.py index 32be7cd..da5d9bd 100644 --- a/knowit/__init__.py +++ b/knowit/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """Know your media files better.""" -from __future__ import unicode_literals __title__ = 'knowit' __version__ = '0.3.0-dev' diff --git a/knowit/__main__.py b/knowit/__main__.py index 310b837..1873c1c 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import json import logging diff --git a/knowit/api.py b/knowit/api.py index 4cfec02..8e0b1bd 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import traceback import typing diff --git a/knowit/config.py b/knowit/config.py index f858ff6..dec1077 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import typing from logging import NullHandler, getLogger diff --git a/knowit/core.py b/knowit/core.py index cf8804b..ce0a001 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from logging import NullHandler, getLogger diff --git a/knowit/properties/__init__.py b/knowit/properties/__init__.py index f871bc4..3a56e85 100644 --- a/knowit/properties/__init__.py +++ b/knowit/properties/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .audio import ( AudioChannels, diff --git a/knowit/properties/audio/__init__.py b/knowit/properties/audio/__init__.py index c7a1198..2007358 100644 --- a/knowit/properties/audio/__init__.py +++ b/knowit/properties/audio/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .bitratemode import BitRateMode from .channels import AudioChannels diff --git a/knowit/properties/audio/bitratemode.py b/knowit/properties/audio/bitratemode.py index e96a1c5..39baccc 100644 --- a/knowit/properties/audio/bitratemode.py +++ b/knowit/properties/audio/bitratemode.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py index b1474bb..8752e0e 100644 --- a/knowit/properties/audio/channels.py +++ b/knowit/properties/audio/channels.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Property diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index b527286..653d9ce 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/audio/compression.py b/knowit/properties/audio/compression.py index 0e7cbae..edbc32c 100644 --- a/knowit/properties/audio/compression.py +++ b/knowit/properties/audio/compression.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/audio/profile.py b/knowit/properties/audio/profile.py index 991fcff..eae3f11 100644 --- a/knowit/properties/audio/profile.py +++ b/knowit/properties/audio/profile.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index 3ce7667..c106089 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ..property import Property diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 949820c..41cc379 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import re from datetime import timedelta diff --git a/knowit/properties/language.py b/knowit/properties/language.py index b203c81..e2b160b 100644 --- a/knowit/properties/language.py +++ b/knowit/properties/language.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import babelfish diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index 19bc6a5..6f20f9a 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ..property import Property diff --git a/knowit/properties/subtitle/__init__.py b/knowit/properties/subtitle/__init__.py index b791152..c245524 100644 --- a/knowit/properties/subtitle/__init__.py +++ b/knowit/properties/subtitle/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .format import SubtitleFormat diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle/format.py index 461c0c9..f3fa800 100644 --- a/knowit/properties/subtitle/format.py +++ b/knowit/properties/subtitle/format.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/video/__init__.py b/knowit/properties/video/__init__.py index e823b39..7cd0322 100644 --- a/knowit/properties/video/__init__.py +++ b/knowit/properties/video/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .codec import VideoCodec from .encoder import VideoEncoder diff --git a/knowit/properties/video/codec.py b/knowit/properties/video/codec.py index d1a873c..c47836a 100644 --- a/knowit/properties/video/codec.py +++ b/knowit/properties/video/codec.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/video/encoder.py b/knowit/properties/video/encoder.py index 41bf54a..ca63b83 100644 --- a/knowit/properties/video/encoder.py +++ b/knowit/properties/video/encoder.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py index 1ddbae9..07aa38a 100644 --- a/knowit/properties/video/profile.py +++ b/knowit/properties/video/profile.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index 6be1891..64fbf09 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import re diff --git a/knowit/properties/video/scantype.py b/knowit/properties/video/scantype.py index 60feeb4..6051526 100644 --- a/knowit/properties/video/scantype.py +++ b/knowit/properties/video/scantype.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...property import Configurable diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index 6866e2b..59f9d04 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ..property import Property diff --git a/knowit/property.py b/knowit/property.py index 52e01fe..2ae170b 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from logging import NullHandler, getLogger diff --git a/knowit/provider.py b/knowit/provider.py index bd006f1..09aebb4 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import os from logging import NullHandler, getLogger diff --git a/knowit/providers/__init__.py b/knowit/providers/__init__.py index 66a0075..9272c1c 100644 --- a/knowit/providers/__init__.py +++ b/knowit/providers/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """Provider package.""" -from __future__ import unicode_literals from .enzyme import EnzymeProvider from .ffmpeg import FFmpegProvider diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index bd88a2d..4c9ff61 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals import json import logging diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index bf7b6d2..4cad75e 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import json import logging diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index f2c155d..f97ba32 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import re from ctypes import c_void_p, c_wchar_p diff --git a/knowit/rule.py b/knowit/rule.py index 5365199..e0fa54a 100644 --- a/knowit/rule.py +++ b/knowit/rule.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .core import Reportable diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index 5337062..32331df 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .audio import AtmosRule from .audio import AudioChannelsRule diff --git a/knowit/rules/audio/__init__.py b/knowit/rules/audio/__init__.py index d8a9470..24fc95c 100644 --- a/knowit/rules/audio/__init__.py +++ b/knowit/rules/audio/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .atmos import AtmosRule from .channels import AudioChannelsRule diff --git a/knowit/rules/audio/atmos.py b/knowit/rules/audio/atmos.py index 3e429d8..97d0e06 100644 --- a/knowit/rules/audio/atmos.py +++ b/knowit/rules/audio/atmos.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...rule import Rule diff --git a/knowit/rules/audio/channels.py b/knowit/rules/audio/channels.py index 9caa7bd..1c8488b 100644 --- a/knowit/rules/audio/channels.py +++ b/knowit/rules/audio/channels.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from logging import NullHandler, getLogger diff --git a/knowit/rules/audio/codec.py b/knowit/rules/audio/codec.py index 5690e22..5dd482b 100644 --- a/knowit/rules/audio/codec.py +++ b/knowit/rules/audio/codec.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...rule import Rule diff --git a/knowit/rules/audio/dtshd.py b/knowit/rules/audio/dtshd.py index d44cdf1..d23d1a9 100644 --- a/knowit/rules/audio/dtshd.py +++ b/knowit/rules/audio/dtshd.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...rule import Rule diff --git a/knowit/rules/language.py b/knowit/rules/language.py index 8a51ccf..a2b3376 100644 --- a/knowit/rules/language.py +++ b/knowit/rules/language.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import re from logging import NullHandler, getLogger diff --git a/knowit/rules/subtitle/__init__.py b/knowit/rules/subtitle/__init__.py index eff71d6..03e2eaa 100644 --- a/knowit/rules/subtitle/__init__.py +++ b/knowit/rules/subtitle/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .closedcaption import ClosedCaptionRule from .hearingimpaired import HearingImpairedRule diff --git a/knowit/rules/subtitle/closedcaption.py b/knowit/rules/subtitle/closedcaption.py index 14be06f..a9e5b2d 100644 --- a/knowit/rules/subtitle/closedcaption.py +++ b/knowit/rules/subtitle/closedcaption.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import re diff --git a/knowit/rules/subtitle/hearingimpaired.py b/knowit/rules/subtitle/hearingimpaired.py index 54c4d56..9c72237 100644 --- a/knowit/rules/subtitle/hearingimpaired.py +++ b/knowit/rules/subtitle/hearingimpaired.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import re diff --git a/knowit/rules/video/__init__.py b/knowit/rules/video/__init__.py index 77c0b40..4a9c03f 100644 --- a/knowit/rules/video/__init__.py +++ b/knowit/rules/video/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from .resolution import ResolutionRule diff --git a/knowit/rules/video/resolution.py b/knowit/rules/video/resolution.py index bcdd594..75a3759 100644 --- a/knowit/rules/video/resolution.py +++ b/knowit/rules/video/resolution.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from ...rule import Rule diff --git a/knowit/serializer.py b/knowit/serializer.py index e8ce363..8d6761f 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import json from datetime import timedelta diff --git a/knowit/utils.py b/knowit/utils.py index 414c348..1cdec38 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import os import sys diff --git a/tests/__init__.py b/tests/__init__.py index dad92b2..fb054f5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import json import os diff --git a/tests/conftest.py b/tests/conftest.py index 6df9733..1513e8c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from pymediainfo import MediaInfo import pytest diff --git a/tests/test_audiochannels.py b/tests/test_audiochannels.py index 79e6766..4493686 100644 --- a/tests/test_audiochannels.py +++ b/tests/test_audiochannels.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import pytest from knowit.rules import AudioChannelsRule diff --git a/tests/test_enzyme.py b/tests/test_enzyme.py index 5985398..449f546 100644 --- a/tests/test_enzyme.py +++ b/tests/test_enzyme.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import pytest from knowit import KnowitException, know diff --git a/tests/test_ffmpeg.py b/tests/test_ffmpeg.py index fc8864b..dcd2661 100644 --- a/tests/test_ffmpeg.py +++ b/tests/test_ffmpeg.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import pytest diff --git a/tests/test_mediainfo.py b/tests/test_mediainfo.py index d87fe3b..eda7330 100644 --- a/tests/test_mediainfo.py +++ b/tests/test_mediainfo.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import pytest diff --git a/tests/test_properties.py b/tests/test_properties.py index 07a2f75..13ec4a6 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import pytest diff --git a/tests/test_resolution.py b/tests/test_resolution.py index 1c61280..af0785a 100644 --- a/tests/test_resolution.py +++ b/tests/test_resolution.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import pytest From 36e46ae1c4428a63b77face29e104b4e761686c8 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 12:15:16 -0500 Subject: [PATCH 010/102] Remove obsolete file encoding --- knowit/__init__.py | 1 - knowit/__main__.py | 1 - knowit/api.py | 1 - knowit/config.py | 1 - knowit/core.py | 1 - knowit/properties/__init__.py | 1 - knowit/properties/audio/__init__.py | 1 - knowit/properties/audio/bitratemode.py | 1 - knowit/properties/audio/channels.py | 1 - knowit/properties/audio/codec.py | 1 - knowit/properties/audio/compression.py | 1 - knowit/properties/audio/profile.py | 1 - knowit/properties/basic.py | 1 - knowit/properties/duration.py | 1 - knowit/properties/language.py | 1 - knowit/properties/quantity.py | 1 - knowit/properties/subtitle/__init__.py | 1 - knowit/properties/subtitle/format.py | 1 - knowit/properties/video/__init__.py | 1 - knowit/properties/video/codec.py | 1 - knowit/properties/video/encoder.py | 1 - knowit/properties/video/profile.py | 1 - knowit/properties/video/ratio.py | 1 - knowit/properties/video/scantype.py | 1 - knowit/properties/yesno.py | 1 - knowit/property.py | 1 - knowit/provider.py | 1 - knowit/providers/__init__.py | 1 - knowit/providers/enzyme.py | 1 - knowit/providers/ffmpeg.py | 1 - knowit/providers/mediainfo.py | 1 - knowit/rule.py | 1 - knowit/rules/__init__.py | 1 - knowit/rules/audio/__init__.py | 1 - knowit/rules/audio/atmos.py | 1 - knowit/rules/audio/channels.py | 1 - knowit/rules/audio/codec.py | 1 - knowit/rules/audio/dtshd.py | 1 - knowit/rules/language.py | 1 - knowit/rules/subtitle/__init__.py | 1 - knowit/rules/subtitle/closedcaption.py | 1 - knowit/rules/subtitle/hearingimpaired.py | 1 - knowit/rules/video/__init__.py | 1 - knowit/rules/video/resolution.py | 1 - knowit/serializer.py | 1 - knowit/units.py | 1 - knowit/utils.py | 1 - tests/__init__.py | 1 - tests/conftest.py | 1 - tests/test_audiochannels.py | 1 - tests/test_enzyme.py | 1 - tests/test_ffmpeg.py | 1 - tests/test_mediainfo.py | 1 - tests/test_properties.py | 1 - tests/test_resolution.py | 1 - 55 files changed, 55 deletions(-) diff --git a/knowit/__init__.py b/knowit/__init__.py index da5d9bd..fbcecf9 100644 --- a/knowit/__init__.py +++ b/knowit/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Know your media files better.""" __title__ = 'knowit' diff --git a/knowit/__main__.py b/knowit/__main__.py index 1873c1c..9612e48 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import json import logging diff --git a/knowit/api.py b/knowit/api.py index 8e0b1bd..999a0d8 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import traceback import typing diff --git a/knowit/config.py b/knowit/config.py index dec1077..9807249 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import typing from logging import NullHandler, getLogger diff --git a/knowit/core.py b/knowit/core.py index ce0a001..88c7a66 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from logging import NullHandler, getLogger diff --git a/knowit/properties/__init__.py b/knowit/properties/__init__.py index 3a56e85..06000fb 100644 --- a/knowit/properties/__init__.py +++ b/knowit/properties/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from .audio import ( AudioChannels, diff --git a/knowit/properties/audio/__init__.py b/knowit/properties/audio/__init__.py index 2007358..a17f9d1 100644 --- a/knowit/properties/audio/__init__.py +++ b/knowit/properties/audio/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from .bitratemode import BitRateMode from .channels import AudioChannels diff --git a/knowit/properties/audio/bitratemode.py b/knowit/properties/audio/bitratemode.py index 39baccc..6a5cad5 100644 --- a/knowit/properties/audio/bitratemode.py +++ b/knowit/properties/audio/bitratemode.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py index 8752e0e..91e1bd9 100644 --- a/knowit/properties/audio/channels.py +++ b/knowit/properties/audio/channels.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Property diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index 653d9ce..d4b6f3d 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/audio/compression.py b/knowit/properties/audio/compression.py index edbc32c..c96b313 100644 --- a/knowit/properties/audio/compression.py +++ b/knowit/properties/audio/compression.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/audio/profile.py b/knowit/properties/audio/profile.py index eae3f11..6be937f 100644 --- a/knowit/properties/audio/profile.py +++ b/knowit/properties/audio/profile.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index c106089..eec83d9 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ..property import Property diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 41cc379..d861312 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re from datetime import timedelta diff --git a/knowit/properties/language.py b/knowit/properties/language.py index e2b160b..2376c8b 100644 --- a/knowit/properties/language.py +++ b/knowit/properties/language.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import babelfish diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index 6f20f9a..41005e0 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ..property import Property diff --git a/knowit/properties/subtitle/__init__.py b/knowit/properties/subtitle/__init__.py index c245524..6d1856f 100644 --- a/knowit/properties/subtitle/__init__.py +++ b/knowit/properties/subtitle/__init__.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- from .format import SubtitleFormat diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle/format.py index f3fa800..63eaacd 100644 --- a/knowit/properties/subtitle/format.py +++ b/knowit/properties/subtitle/format.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/video/__init__.py b/knowit/properties/video/__init__.py index 7cd0322..7ed3cdc 100644 --- a/knowit/properties/video/__init__.py +++ b/knowit/properties/video/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from .codec import VideoCodec from .encoder import VideoEncoder diff --git a/knowit/properties/video/codec.py b/knowit/properties/video/codec.py index c47836a..84d0bb0 100644 --- a/knowit/properties/video/codec.py +++ b/knowit/properties/video/codec.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/video/encoder.py b/knowit/properties/video/encoder.py index ca63b83..522da9a 100644 --- a/knowit/properties/video/encoder.py +++ b/knowit/properties/video/encoder.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py index 07aa38a..7706d50 100644 --- a/knowit/properties/video/profile.py +++ b/knowit/properties/video/profile.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index 64fbf09..89d5066 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re diff --git a/knowit/properties/video/scantype.py b/knowit/properties/video/scantype.py index 6051526..57883d3 100644 --- a/knowit/properties/video/scantype.py +++ b/knowit/properties/video/scantype.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...property import Configurable diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index 59f9d04..f26db9d 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ..property import Property diff --git a/knowit/property.py b/knowit/property.py index 2ae170b..2ecfd96 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from logging import NullHandler, getLogger diff --git a/knowit/provider.py b/knowit/provider.py index 09aebb4..9be5310 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os from logging import NullHandler, getLogger diff --git a/knowit/providers/__init__.py b/knowit/providers/__init__.py index 9272c1c..4e409e0 100644 --- a/knowit/providers/__init__.py +++ b/knowit/providers/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Provider package.""" from .enzyme import EnzymeProvider diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index 4c9ff61..28a8a58 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import json import logging diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index 4cad75e..1085eec 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import json import logging diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index f97ba32..d42707c 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re from ctypes import c_void_p, c_wchar_p diff --git a/knowit/rule.py b/knowit/rule.py index e0fa54a..7d63f62 100644 --- a/knowit/rule.py +++ b/knowit/rule.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from .core import Reportable diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index 32331df..7bd7195 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from .audio import AtmosRule from .audio import AudioChannelsRule diff --git a/knowit/rules/audio/__init__.py b/knowit/rules/audio/__init__.py index 24fc95c..bc8a5fa 100644 --- a/knowit/rules/audio/__init__.py +++ b/knowit/rules/audio/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from .atmos import AtmosRule from .channels import AudioChannelsRule diff --git a/knowit/rules/audio/atmos.py b/knowit/rules/audio/atmos.py index 97d0e06..a8a0914 100644 --- a/knowit/rules/audio/atmos.py +++ b/knowit/rules/audio/atmos.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...rule import Rule diff --git a/knowit/rules/audio/channels.py b/knowit/rules/audio/channels.py index 1c8488b..ab2d925 100644 --- a/knowit/rules/audio/channels.py +++ b/knowit/rules/audio/channels.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from logging import NullHandler, getLogger diff --git a/knowit/rules/audio/codec.py b/knowit/rules/audio/codec.py index 5dd482b..62c8941 100644 --- a/knowit/rules/audio/codec.py +++ b/knowit/rules/audio/codec.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...rule import Rule diff --git a/knowit/rules/audio/dtshd.py b/knowit/rules/audio/dtshd.py index d23d1a9..7a9ebde 100644 --- a/knowit/rules/audio/dtshd.py +++ b/knowit/rules/audio/dtshd.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...rule import Rule diff --git a/knowit/rules/language.py b/knowit/rules/language.py index a2b3376..fbc633d 100644 --- a/knowit/rules/language.py +++ b/knowit/rules/language.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re from logging import NullHandler, getLogger diff --git a/knowit/rules/subtitle/__init__.py b/knowit/rules/subtitle/__init__.py index 03e2eaa..be229e0 100644 --- a/knowit/rules/subtitle/__init__.py +++ b/knowit/rules/subtitle/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from .closedcaption import ClosedCaptionRule from .hearingimpaired import HearingImpairedRule diff --git a/knowit/rules/subtitle/closedcaption.py b/knowit/rules/subtitle/closedcaption.py index a9e5b2d..40e07d1 100644 --- a/knowit/rules/subtitle/closedcaption.py +++ b/knowit/rules/subtitle/closedcaption.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re diff --git a/knowit/rules/subtitle/hearingimpaired.py b/knowit/rules/subtitle/hearingimpaired.py index 9c72237..d1e9e7e 100644 --- a/knowit/rules/subtitle/hearingimpaired.py +++ b/knowit/rules/subtitle/hearingimpaired.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re diff --git a/knowit/rules/video/__init__.py b/knowit/rules/video/__init__.py index 4a9c03f..0ea4a73 100644 --- a/knowit/rules/video/__init__.py +++ b/knowit/rules/video/__init__.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- from .resolution import ResolutionRule diff --git a/knowit/rules/video/resolution.py b/knowit/rules/video/resolution.py index 75a3759..ec0e67a 100644 --- a/knowit/rules/video/resolution.py +++ b/knowit/rules/video/resolution.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...rule import Rule diff --git a/knowit/serializer.py b/knowit/serializer.py index 8d6761f..31b8436 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import json from datetime import timedelta diff --git a/knowit/units.py b/knowit/units.py index 2397a60..5091984 100644 --- a/knowit/units.py +++ b/knowit/units.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- def _build_unit_registry(): diff --git a/knowit/utils.py b/knowit/utils.py index 1cdec38..1b5afdd 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import sys diff --git a/tests/__init__.py b/tests/__init__.py index fb054f5..02ef168 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import json import os diff --git a/tests/conftest.py b/tests/conftest.py index 1513e8c..dda3f68 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from pymediainfo import MediaInfo import pytest diff --git a/tests/test_audiochannels.py b/tests/test_audiochannels.py index 4493686..9b1d700 100644 --- a/tests/test_audiochannels.py +++ b/tests/test_audiochannels.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest from knowit.rules import AudioChannelsRule diff --git a/tests/test_enzyme.py b/tests/test_enzyme.py index 449f546..5ba1c6d 100644 --- a/tests/test_enzyme.py +++ b/tests/test_enzyme.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest from knowit import KnowitException, know diff --git a/tests/test_ffmpeg.py b/tests/test_ffmpeg.py index dcd2661..9eb675e 100644 --- a/tests/test_ffmpeg.py +++ b/tests/test_ffmpeg.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/test_mediainfo.py b/tests/test_mediainfo.py index eda7330..25108c0 100644 --- a/tests/test_mediainfo.py +++ b/tests/test_mediainfo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/test_properties.py b/tests/test_properties.py index 13ec4a6..5f5dddc 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/test_resolution.py b/tests/test_resolution.py index af0785a..9e66813 100644 --- a/tests/test_resolution.py +++ b/tests/test_resolution.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest From 7cd0defa5b77eab2bc8ceff8c5adf8b94c27fc12 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 02:09:18 -0400 Subject: [PATCH 011/102] Use explicit imports --- knowit/__init__.py | 2 +- knowit/__main__.py | 8 ++++---- knowit/api.py | 6 +++--- knowit/config.py | 2 +- knowit/properties/__init__.py | 16 ++++++++-------- knowit/properties/audio/__init__.py | 10 +++++----- knowit/properties/audio/bitratemode.py | 2 +- knowit/properties/audio/channels.py | 2 +- knowit/properties/audio/codec.py | 2 +- knowit/properties/audio/compression.py | 2 +- knowit/properties/audio/profile.py | 2 +- knowit/properties/basic.py | 2 +- knowit/properties/duration.py | 2 +- knowit/properties/language.py | 2 +- knowit/properties/quantity.py | 2 +- knowit/properties/subtitle/__init__.py | 2 +- knowit/properties/subtitle/format.py | 2 +- knowit/properties/video/__init__.py | 14 +++++++------- knowit/properties/video/codec.py | 2 +- knowit/properties/video/encoder.py | 2 +- knowit/properties/video/profile.py | 2 +- knowit/properties/video/ratio.py | 2 +- knowit/properties/video/scantype.py | 2 +- knowit/properties/yesno.py | 2 +- knowit/property.py | 2 +- knowit/provider.py | 4 ++-- knowit/providers/__init__.py | 6 +++--- knowit/providers/enzyme.py | 14 +++++++------- knowit/providers/ffmpeg.py | 16 ++++++++-------- knowit/rule.py | 2 +- knowit/rules/__init__.py | 16 ++++++++-------- knowit/rules/audio/__init__.py | 8 ++++---- knowit/rules/audio/atmos.py | 2 +- knowit/rules/audio/channels.py | 2 +- knowit/rules/audio/codec.py | 2 +- knowit/rules/audio/dtshd.py | 2 +- knowit/rules/language.py | 2 +- knowit/rules/subtitle/__init__.py | 4 ++-- knowit/rules/subtitle/closedcaption.py | 2 +- knowit/rules/subtitle/hearingimpaired.py | 2 +- knowit/rules/video/__init__.py | 2 +- knowit/rules/video/resolution.py | 2 +- knowit/serializer.py | 2 +- knowit/utils.py | 2 +- 44 files changed, 93 insertions(+), 93 deletions(-) diff --git a/knowit/__init__.py b/knowit/__init__.py index fbcecf9..a5fdef4 100644 --- a/knowit/__init__.py +++ b/knowit/__init__.py @@ -17,4 +17,4 @@ '.omf', '.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', '.vivo', '.vob', '.vro', '.webm', '.wm', '.wmv', '.wmx', '.wrap', '.wvx', '.wx', '.x264', '.xvid') -from .api import KnowitException, know +from knowit.api import KnowitException, know diff --git a/knowit/__main__.py b/knowit/__main__.py index 9612e48..026b001 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -6,17 +6,17 @@ import yaml -from . import ( +from knowit import ( __url__, __version__, api, ) -from .provider import ProviderError -from .serializer import ( +from knowit.provider import ProviderError +from knowit.serializer import ( get_json_encoder, get_yaml_dumper, ) -from .utils import recurse_paths +from knowit.utils import recurse_paths logging.basicConfig(stream=sys.stdout, format='%(message)s') logging.getLogger('CONSOLE').setLevel(logging.INFO) diff --git a/knowit/api.py b/knowit/api.py index 999a0d8..3dcbd3f 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -2,9 +2,9 @@ import traceback import typing -from . import __version__ -from .config import Config -from .provider import Provider +from knowit import __version__ +from knowit.config import Config +from knowit.provider import Provider from .providers import ( EnzymeProvider, FFmpegProvider, diff --git a/knowit/config.py b/knowit/config.py index 9807249..be3ae12 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -5,7 +5,7 @@ from pkg_resources import resource_stream import yaml -from .serializer import get_yaml_loader +from knowit.serializer import get_yaml_loader logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/properties/__init__.py b/knowit/properties/__init__.py index 06000fb..00771b8 100644 --- a/knowit/properties/__init__.py +++ b/knowit/properties/__init__.py @@ -1,19 +1,19 @@ -from .audio import ( +from knowit.properties.audio import ( AudioChannels, AudioCodec, AudioCompression, AudioProfile, BitRateMode, ) -from .basic import Basic -from .duration import Duration -from .language import Language -from .quantity import Quantity -from .subtitle import ( +from knowit.properties.basic import Basic +from knowit.properties.duration import Duration +from knowit.properties.language import Language +from knowit.properties.quantity import Quantity +from knowit.properties.subtitle import ( SubtitleFormat, ) -from .video import ( +from knowit.properties.video import ( Ratio, ScanType, VideoCodec, @@ -22,4 +22,4 @@ VideoProfileLevel, VideoProfileTier, ) -from .yesno import YesNo +from knowit.properties.yesno import YesNo diff --git a/knowit/properties/audio/__init__.py b/knowit/properties/audio/__init__.py index a17f9d1..31d30cb 100644 --- a/knowit/properties/audio/__init__.py +++ b/knowit/properties/audio/__init__.py @@ -1,6 +1,6 @@ -from .bitratemode import BitRateMode -from .channels import AudioChannels -from .codec import AudioCodec -from .compression import AudioCompression -from .profile import AudioProfile +from knowit.properties.audio.bitratemode import BitRateMode +from knowit.properties.audio.channels import AudioChannels +from knowit.properties.audio.codec import AudioCodec +from knowit.properties.audio.compression import AudioCompression +from knowit.properties.audio.profile import AudioProfile diff --git a/knowit/properties/audio/bitratemode.py b/knowit/properties/audio/bitratemode.py index 6a5cad5..4771d2a 100644 --- a/knowit/properties/audio/bitratemode.py +++ b/knowit/properties/audio/bitratemode.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class BitRateMode(Configurable): diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py index 91e1bd9..4d1fd5d 100644 --- a/knowit/properties/audio/channels.py +++ b/knowit/properties/audio/channels.py @@ -1,5 +1,5 @@ -from ...property import Property +from knowit.property import Property class AudioChannels(Property): diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index d4b6f3d..df3ed46 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class AudioCodec(Configurable): diff --git a/knowit/properties/audio/compression.py b/knowit/properties/audio/compression.py index c96b313..2317793 100644 --- a/knowit/properties/audio/compression.py +++ b/knowit/properties/audio/compression.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class AudioCompression(Configurable): diff --git a/knowit/properties/audio/profile.py b/knowit/properties/audio/profile.py index 6be937f..4ef833a 100644 --- a/knowit/properties/audio/profile.py +++ b/knowit/properties/audio/profile.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class AudioProfile(Configurable): diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index eec83d9..23c3b52 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -1,5 +1,5 @@ -from ..property import Property +from knowit.property import Property class Basic(Property): diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index d861312..acbd642 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -2,7 +2,7 @@ import re from datetime import timedelta -from ..property import Property +from knowit.property import Property class Duration(Property): diff --git a/knowit/properties/language.py b/knowit/properties/language.py index 2376c8b..6c04627 100644 --- a/knowit/properties/language.py +++ b/knowit/properties/language.py @@ -1,7 +1,7 @@ import babelfish -from ..property import Property +from knowit.property import Property class Language(Property): diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index 41005e0..64c6fc2 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -1,5 +1,5 @@ -from ..property import Property +from knowit.property import Property class Quantity(Property): diff --git a/knowit/properties/subtitle/__init__.py b/knowit/properties/subtitle/__init__.py index 6d1856f..9525628 100644 --- a/knowit/properties/subtitle/__init__.py +++ b/knowit/properties/subtitle/__init__.py @@ -1,2 +1,2 @@ -from .format import SubtitleFormat +from knowit.properties.subtitle.format import SubtitleFormat diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle/format.py index 63eaacd..088473d 100644 --- a/knowit/properties/subtitle/format.py +++ b/knowit/properties/subtitle/format.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class SubtitleFormat(Configurable): diff --git a/knowit/properties/video/__init__.py b/knowit/properties/video/__init__.py index 7ed3cdc..5cd2836 100644 --- a/knowit/properties/video/__init__.py +++ b/knowit/properties/video/__init__.py @@ -1,8 +1,8 @@ -from .codec import VideoCodec -from .encoder import VideoEncoder -from .profile import VideoProfile -from .profile import VideoProfileLevel -from .profile import VideoProfileTier -from .ratio import Ratio -from .scantype import ScanType +from knowit.properties.video.codec import VideoCodec +from knowit.properties.video.encoder import VideoEncoder +from knowit.properties.video.profile import VideoProfile +from knowit.properties.video.profile import VideoProfileLevel +from knowit.properties.video.profile import VideoProfileTier +from knowit.properties.video.ratio import Ratio +from knowit.properties.video.scantype import ScanType diff --git a/knowit/properties/video/codec.py b/knowit/properties/video/codec.py index 84d0bb0..abd2f12 100644 --- a/knowit/properties/video/codec.py +++ b/knowit/properties/video/codec.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class VideoCodec(Configurable): diff --git a/knowit/properties/video/encoder.py b/knowit/properties/video/encoder.py index 522da9a..58d122a 100644 --- a/knowit/properties/video/encoder.py +++ b/knowit/properties/video/encoder.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class VideoEncoder(Configurable): diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py index 7706d50..34f6acd 100644 --- a/knowit/properties/video/profile.py +++ b/knowit/properties/video/profile.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class VideoProfile(Configurable): diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index 89d5066..5946e6c 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -1,7 +1,7 @@ import re -from ...property import Property +from knowit.property import Property class Ratio(Property): diff --git a/knowit/properties/video/scantype.py b/knowit/properties/video/scantype.py index 57883d3..1367796 100644 --- a/knowit/properties/video/scantype.py +++ b/knowit/properties/video/scantype.py @@ -1,5 +1,5 @@ -from ...property import Configurable +from knowit.property import Configurable class ScanType(Configurable): diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index f26db9d..c8ad67d 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -1,5 +1,5 @@ -from ..property import Property +from knowit.property import Property class YesNo(Property): diff --git a/knowit/property.py b/knowit/property.py index 2ecfd96..5c62f36 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -1,7 +1,7 @@ from logging import NullHandler, getLogger -from .core import Reportable +from knowit.core import Reportable logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/provider.py b/knowit/provider.py index 9be5310..3a3fd7d 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -2,8 +2,8 @@ import os from logging import NullHandler, getLogger -from .properties import Quantity -from .units import units +from knowit.properties import Quantity +from knowit.units import units logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/providers/__init__.py b/knowit/providers/__init__.py index 4e409e0..4224348 100644 --- a/knowit/providers/__init__.py +++ b/knowit/providers/__init__.py @@ -1,5 +1,5 @@ """Provider package.""" -from .enzyme import EnzymeProvider -from .ffmpeg import FFmpegProvider -from .mediainfo import MediaInfoProvider +from knowit.providers.enzyme import EnzymeProvider +from knowit.providers.ffmpeg import FFmpegProvider +from knowit.providers.mediainfo import MediaInfoProvider diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index 28a8a58..99854ce 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -5,7 +5,7 @@ from logging import NullHandler, getLogger import enzyme -from ..properties import ( +from knowit.properties import ( AudioCodec, Basic, Duration, @@ -14,21 +14,21 @@ VideoCodec, YesNo, ) -from ..property import Property -from ..provider import ( +from knowit.property import Property +from knowit.provider import ( MalformedFileError, Provider, ) -from ..rules import ( +from knowit.rules import ( AudioChannelsRule, ClosedCaptionRule, HearingImpairedRule, LanguageRule, ResolutionRule, ) -from ..serializer import get_json_encoder -from ..units import units -from ..utils import todict +from knowit.serializer import get_json_encoder +from knowit.units import units +from knowit.utils import todict logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index 1085eec..2e9062b 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -5,8 +5,8 @@ from logging import NullHandler, getLogger from subprocess import check_output -from .. import VIDEO_EXTENSIONS -from ..properties import ( +from knowit import VIDEO_EXTENSIONS +from knowit.properties import ( AudioChannels, AudioCodec, AudioProfile, @@ -22,14 +22,14 @@ VideoProfileLevel, YesNo, ) -from ..property import ( +from knowit.property import ( Property, ) -from ..provider import ( +from knowit.provider import ( MalformedFileError, Provider, ) -from ..rules import ( +from knowit.rules import ( AudioChannelsRule, AudioCodecRule, ClosedCaptionRule, @@ -37,9 +37,9 @@ LanguageRule, ResolutionRule, ) -from ..serializer import get_json_encoder -from ..units import units -from ..utils import ( +from knowit.serializer import get_json_encoder +from knowit.units import units +from knowit.utils import ( define_candidate, detect_os, ) diff --git a/knowit/rule.py b/knowit/rule.py index 7d63f62..b3e715f 100644 --- a/knowit/rule.py +++ b/knowit/rule.py @@ -1,5 +1,5 @@ -from .core import Reportable +from knowit.core import Reportable class Rule(Reportable): diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index 7bd7195..8299e49 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -1,9 +1,9 @@ -from .audio import AtmosRule -from .audio import AudioChannelsRule -from .audio import AudioCodecRule -from .audio import DtsHdRule -from .language import LanguageRule -from .subtitle import ClosedCaptionRule -from .subtitle import HearingImpairedRule -from .video import ResolutionRule +from knowit.rules.audio import AtmosRule +from knowit.rules.audio import AudioChannelsRule +from knowit.rules.audio import AudioCodecRule +from knowit.rules.audio import DtsHdRule +from knowit.rules.language import LanguageRule +from knowit.rules.subtitle import ClosedCaptionRule +from knowit.rules.subtitle import HearingImpairedRule +from knowit.rules.video import ResolutionRule diff --git a/knowit/rules/audio/__init__.py b/knowit/rules/audio/__init__.py index bc8a5fa..d0705eb 100644 --- a/knowit/rules/audio/__init__.py +++ b/knowit/rules/audio/__init__.py @@ -1,5 +1,5 @@ -from .atmos import AtmosRule -from .channels import AudioChannelsRule -from .codec import AudioCodecRule -from .dtshd import DtsHdRule +from knowit.rules.audio.atmos import AtmosRule +from knowit.rules.audio.channels import AudioChannelsRule +from knowit.rules.audio.codec import AudioCodecRule +from knowit.rules.audio.dtshd import DtsHdRule diff --git a/knowit/rules/audio/atmos.py b/knowit/rules/audio/atmos.py index a8a0914..3c53fac 100644 --- a/knowit/rules/audio/atmos.py +++ b/knowit/rules/audio/atmos.py @@ -1,5 +1,5 @@ -from ...rule import Rule +from knowit.rule import Rule class AtmosRule(Rule): diff --git a/knowit/rules/audio/channels.py b/knowit/rules/audio/channels.py index ab2d925..5a85315 100644 --- a/knowit/rules/audio/channels.py +++ b/knowit/rules/audio/channels.py @@ -1,7 +1,7 @@ from logging import NullHandler, getLogger -from ...rule import Rule +from knowit.rule import Rule logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/rules/audio/codec.py b/knowit/rules/audio/codec.py index 62c8941..a6444dc 100644 --- a/knowit/rules/audio/codec.py +++ b/knowit/rules/audio/codec.py @@ -1,5 +1,5 @@ -from ...rule import Rule +from knowit.rule import Rule class AudioCodecRule(Rule): diff --git a/knowit/rules/audio/dtshd.py b/knowit/rules/audio/dtshd.py index 7a9ebde..d45e9e9 100644 --- a/knowit/rules/audio/dtshd.py +++ b/knowit/rules/audio/dtshd.py @@ -1,5 +1,5 @@ -from ...rule import Rule +from knowit.rule import Rule class DtsHdRule(Rule): diff --git a/knowit/rules/language.py b/knowit/rules/language.py index fbc633d..9a7355a 100644 --- a/knowit/rules/language.py +++ b/knowit/rules/language.py @@ -4,7 +4,7 @@ import babelfish -from ..rule import Rule +from knowit.rule import Rule logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/rules/subtitle/__init__.py b/knowit/rules/subtitle/__init__.py index be229e0..55ef30a 100644 --- a/knowit/rules/subtitle/__init__.py +++ b/knowit/rules/subtitle/__init__.py @@ -1,3 +1,3 @@ -from .closedcaption import ClosedCaptionRule -from .hearingimpaired import HearingImpairedRule +from knowit.rules.subtitle.closedcaption import ClosedCaptionRule +from knowit.rules.subtitle.hearingimpaired import HearingImpairedRule diff --git a/knowit/rules/subtitle/closedcaption.py b/knowit/rules/subtitle/closedcaption.py index 40e07d1..e8aa405 100644 --- a/knowit/rules/subtitle/closedcaption.py +++ b/knowit/rules/subtitle/closedcaption.py @@ -1,7 +1,7 @@ import re -from ...rule import Rule +from knowit.rule import Rule class ClosedCaptionRule(Rule): diff --git a/knowit/rules/subtitle/hearingimpaired.py b/knowit/rules/subtitle/hearingimpaired.py index d1e9e7e..c96915e 100644 --- a/knowit/rules/subtitle/hearingimpaired.py +++ b/knowit/rules/subtitle/hearingimpaired.py @@ -1,7 +1,7 @@ import re -from ...rule import Rule +from knowit.rule import Rule class HearingImpairedRule(Rule): diff --git a/knowit/rules/video/__init__.py b/knowit/rules/video/__init__.py index 0ea4a73..e453f17 100644 --- a/knowit/rules/video/__init__.py +++ b/knowit/rules/video/__init__.py @@ -1,2 +1,2 @@ -from .resolution import ResolutionRule +from knowit.rules.video.resolution import ResolutionRule diff --git a/knowit/rules/video/resolution.py b/knowit/rules/video/resolution.py index ec0e67a..24233fb 100644 --- a/knowit/rules/video/resolution.py +++ b/knowit/rules/video/resolution.py @@ -1,5 +1,5 @@ -from ...rule import Rule +from knowit.rule import Rule class ResolutionRule(Rule): diff --git a/knowit/serializer.py b/knowit/serializer.py index 31b8436..e1f030a 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -5,7 +5,7 @@ import babelfish import yaml -from .units import units +from knowit.units import units def format_property(context, o): diff --git a/knowit/utils.py b/knowit/utils.py index 1b5afdd..7da4fab 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -2,7 +2,7 @@ import os import sys -from . import VIDEO_EXTENSIONS +from knowit import VIDEO_EXTENSIONS def recurse_paths(paths): From eb4afb433e5a473e3e0966be76969f4831a10b2c Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 01:57:22 -0400 Subject: [PATCH 012/102] Add tests for detect_os --- tests/test_utils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/test_utils.py diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..7f5b47c --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,23 @@ +from unittest.mock import patch + +import pytest + +from knowit.utils import detect_os + + +@pytest.mark.parametrize( + 'os_name, sys_platform, expected', [ + ('nt', None, 'windows'), + ('dos', None, 'windows'), + ('os2', None, 'windows'), + ('ce', None, 'windows'), + (None, 'darwin', 'macos'), + (None, None, 'unix'), + ] +) +def test_detect_os_is_windows(os_name, sys_platform, expected): + with patch('knowit.utils.os') as mock_os: + mock_os.name = os_name + with patch('knowit.utils.sys') as mock_sys: + mock_sys.platform = sys_platform + assert detect_os() == expected From 8389e0721ce887db22a139c59cf116a18ff1887f Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 28 Feb 2021 12:27:39 -0500 Subject: [PATCH 013/102] Use f-strings instead of str.format --- knowit/api.py | 6 +++--- knowit/providers/ffmpeg.py | 3 ++- knowit/providers/mediainfo.py | 3 ++- knowit/rules/video/resolution.py | 5 ++--- knowit/serializer.py | 9 ++++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/knowit/api.py b/knowit/api.py index 3dcbd3f..b2a9821 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -88,13 +88,13 @@ def dependencies(context=None): def _centered(value): value = value[-52:] - return '| {msg:^53} |'.format(msg=value) + return f'| {value:^53} |' def debug_info(context=None, exc_info=False): lines = [ '+-------------------------------------------------------+', - _centered('KnowIt {0}'.format(__version__)), + _centered(f'KnowIt {__version__}'), '+-------------------------------------------------------+' ] @@ -114,7 +114,7 @@ def debug_info(context=None, exc_info=False): lines.append('+-------------------------------------------------------+') for k, v in context.items(): if v: - lines.append(_centered('{}: {}'.format(k, v))) + lines.append(_centered(f'{k}: {v}')) if debug_data: lines.append('+-------------------------------------------------------+') diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index 2e9062b..b8edd2f 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -265,5 +265,6 @@ def version(self): """Return ffmpeg version information.""" if not self.executor: return {} + version = '.'.join(map(str, self.executor.version)) - return {self.executor.location: 'v{}'.format('.'.join(map(str, self.executor.version)))} + return {self.executor.location: f'v{version}'} diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index d42707c..79850ea 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -324,5 +324,6 @@ def version(self): """Return mediainfo version information.""" versions = {'pymediainfo': pymediainfo_version} if self.executor: - versions[self.executor.location] = 'v{}'.format('.'.join(map(str, self.executor.version))) + executor_version = '.'.join(map(str, self.executor.version)) + versions[self.executor.location] = f'v{executor_version}' return versions diff --git a/knowit/rules/video/resolution.py b/knowit/rules/video/resolution.py index 24233fb..5a2ac81 100644 --- a/knowit/rules/video/resolution.py +++ b/knowit/rules/video/resolution.py @@ -66,8 +66,7 @@ def execute(self, props, pv_props, context): selected_resolution = r if selected_resolution: - return '{0}{1}'.format(selected_resolution, scan_type) + return f'{selected_resolution}{scan_type}' - msg = '{width}x{height} - scan_type: {scan_type}, aspect_ratio: {dar}, pixel_aspect_ratio: {par}'.format( - width=width, height=height, scan_type=scan_type, dar=dar, par=par) + msg = f'{width}x{height} - scan_type: {scan_type}, aspect_ratio: {dar}, pixel_aspect_ratio: {par}' self.report(msg, context) diff --git a/knowit/serializer.py b/knowit/serializer.py index e1f030a..5b4efac 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -95,13 +95,12 @@ def format_duration(duration, profile='default'): seconds = int(seconds - (minutes * 60)) if profile == 'human': if hours > 0: - return '{0} hours {1:02d} minutes {2:02d} seconds'.format(hours, minutes, seconds) + return f'{hours} hours {minutes:02d} minutes { seconds:02d} seconds' if minutes > 0: - return '{0} minutes {1:02d} seconds'.format(minutes, seconds) + return f'{minutes} minutes {seconds:02d} seconds' + return f'{seconds} seconds' - return '{0} seconds'.format(seconds) - - return '{0}:{1:02d}:{2:02d}'.format(hours, minutes, seconds) + return f'{hours}:{minutes:02d}:{seconds:02d}' def format_language(language, profile='default'): From 057fa2518883744dfc65bc3bf9e27ed24c67c234 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 18:21:07 -0400 Subject: [PATCH 014/102] Fix tests --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index d99cb88..6bacdaf 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ deps = pytest-cov requests >= 2.21.0 commands = + pip install . pytest tests --cov-report term --cov-report html --cov knowit --verbose {posargs} [testenv:lint] From 6a410acb13281c51ffc1e2ba40f120e8fe6c33be Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 18:28:36 -0400 Subject: [PATCH 015/102] Fix error output decoding when getting MediaInfo CLI version --- knowit/providers/mediainfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 79850ea..2789e43 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -139,7 +139,7 @@ def create(cls, os_family=None, suggested_path=None): return MediaInfoCliExecutor(candidate, version) except CalledProcessError as e: # old mediainfo returns non-zero exit code for mediainfo --version - version = cls._get_version(e.output) + version = cls._get_version(e.output.decode()) if version: logger.debug('MediaInfo cli detected: %s', candidate) return MediaInfoCliExecutor(candidate, version) From 053b6946e2177bbc6ef291d40448045665660750 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 19:10:08 -0400 Subject: [PATCH 016/102] Add type-hints --- knowit/utils.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index 7da4fab..76806e9 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -1,18 +1,24 @@ +from __future__ import annotations + import os import sys +import typing from knowit import VIDEO_EXTENSIONS +if sys.version_info < (3, 8): + OS_FAMILY = str +else: + OS_FAMILY = typing.Literal['windows', 'macos', 'unix'] + +OPTION_MAP = typing.Dict[str, typing.Tuple[str]] -def recurse_paths(paths): - """Return a file system encoded list of videofiles. - :param paths: - :type paths: string or list - :return: - :rtype: list - """ +def recurse_paths( + paths: typing.Union[str, typing.Iterable[str]] +) -> typing.List[str]: + """Return a file system encoded list of video files.""" enc_paths = [] if isinstance(paths, str): @@ -35,7 +41,10 @@ def recurse_paths(paths): return [f for f in enc_paths if not (f in seen or seen_add(f))] -def todict(obj, classkey=None): +def todict( + obj: typing.Any, + classkey: typing.Optional[typing.Type] = None +) -> typing.Union[str, dict, list]: """Transform an object to dict.""" if isinstance(obj, str): return obj @@ -58,17 +67,21 @@ def todict(obj, classkey=None): return obj -def detect_os(): +def detect_os() -> OS_FAMILY: """Detect os family: windows, macos or unix.""" if os.name in ('nt', 'dos', 'os2', 'ce'): return 'windows' if sys.platform == 'darwin': return 'macos' - return 'unix' -def define_candidate(locations, names, os_family=None, suggested_path=None): +def define_candidate( + locations: OPTION_MAP, + names: OPTION_MAP, + os_family: typing.Optional[OS_FAMILY] = None, + suggested_path: typing.Optional[str] = None, +) -> typing.Generator[str, None, None]: """Generate candidate list for the given parameters.""" os_family = os_family or detect_os() for location in (suggested_path, ) + locations[os_family]: From a33e895de74b038b24702fe6a93b1ff2be455d0e Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 19:32:13 -0400 Subject: [PATCH 017/102] Fix "str" has not attribute "decode" --- knowit/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index 76806e9..c140293 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -18,13 +18,12 @@ def recurse_paths( paths: typing.Union[str, typing.Iterable[str]] ) -> typing.List[str]: - """Return a file system encoded list of video files.""" + """Return a list of video files.""" enc_paths = [] if isinstance(paths, str): paths = [p.strip() for p in paths.split(',')] if ',' in paths else paths.split() - encoding = sys.getfilesystemencoding() for path in paths: if os.path.isfile(path): enc_paths.append(path) @@ -32,7 +31,7 @@ def recurse_paths( for root, directories, filenames in os.walk(path): for filename in filenames: if os.path.splitext(filename)[1] in VIDEO_EXTENSIONS: - fullpath = os.path.join(root, filename).decode(encoding) + fullpath = os.path.join(root, filename) enc_paths.append(fullpath) # Lets remove any dupes since mediainfo is rather slow. From 90e98b6d104b6b1e3e24131bd544dcb89e78358f Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 19:34:14 -0400 Subject: [PATCH 018/102] Fix mypy needs type annotation for 'seen'. Use the ordered nature of dict in Python 3.6+ to get unique items. --- knowit/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index c140293..83a579b 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -35,9 +35,8 @@ def recurse_paths( enc_paths.append(fullpath) # Lets remove any dupes since mediainfo is rather slow. - seen = set() - seen_add = seen.add - return [f for f in enc_paths if not (f in seen or seen_add(f))] + unique_paths = dict.fromkeys(enc_paths) + return list(unique_paths) def todict( From a59b5037a3f98961303ebd34282bc6dc9871dd34 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 20:04:14 -0400 Subject: [PATCH 019/102] Refactor todict -> to_dict --- knowit/providers/enzyme.py | 4 ++-- knowit/utils.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index 99854ce..632e6d7 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -28,7 +28,7 @@ ) from knowit.serializer import get_json_encoder from knowit.units import units -from knowit.utils import todict +from knowit.utils import to_dict logger = getLogger(__name__) logger.addHandler(NullHandler()) @@ -104,7 +104,7 @@ def accepts(self, video_path): def extract_info(cls, video_path): """Extract info from the video.""" with open(video_path, 'rb') as f: - return todict(enzyme.MKV(f)) + return to_dict(enzyme.MKV(f)) def describe(self, video_path, context): """Return video metadata.""" diff --git a/knowit/utils.py b/knowit/utils.py index 83a579b..559bc2b 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -39,7 +39,7 @@ def recurse_paths( return list(unique_paths) -def todict( +def to_dict( obj: typing.Any, classkey: typing.Optional[typing.Type] = None ) -> typing.Union[str, dict, list]: @@ -49,14 +49,14 @@ def todict( elif isinstance(obj, dict): data = {} for (k, v) in obj.items(): - data[k] = todict(v, classkey) + data[k] = to_dict(v, classkey) return data elif hasattr(obj, '_ast'): - return todict(obj._ast()) + return to_dict(obj._ast()) elif hasattr(obj, '__iter__'): - return [todict(v, classkey) for v in obj] + return [to_dict(v, classkey) for v in obj] elif hasattr(obj, '__dict__'): - values = [(key, todict(value, classkey)) + values = [(key, to_dict(value, classkey)) for key, value in obj.__dict__.items() if not callable(value) and not key.startswith('_')] data = {k: v for k, v in values if v is not None} if classkey is not None and hasattr(obj, '__class__'): From 6377da04ce6033fc3a07f5feae9f9d2dbf06a8d3 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 20:04:42 -0400 Subject: [PATCH 020/102] Refactor fullpath -> full_path --- knowit/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index 559bc2b..3226743 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -31,8 +31,8 @@ def recurse_paths( for root, directories, filenames in os.walk(path): for filename in filenames: if os.path.splitext(filename)[1] in VIDEO_EXTENSIONS: - fullpath = os.path.join(root, filename) - enc_paths.append(fullpath) + full_path = os.path.join(root, filename) + enc_paths.append(full_path) # Lets remove any dupes since mediainfo is rather slow. unique_paths = dict.fromkeys(enc_paths) From 587276aed1b6f75c7594dc5a12b94c666e805c30 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 20:05:48 -0400 Subject: [PATCH 021/102] Fix line spacing --- knowit/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/knowit/utils.py b/knowit/utils.py index 3226743..31b959a 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -1,6 +1,5 @@ from __future__ import annotations - import os import sys import typing From edf946c7799a1f38e05e94cd1b4ccd81b321f0dc Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 20:13:00 -0400 Subject: [PATCH 022/102] Add type-hints --- knowit/__init__.py | 1 - knowit/__main__.py | 131 ++++++++++++++++++++------- knowit/api.py | 34 +++---- knowit/config.py | 4 +- knowit/core.py | 17 +++- knowit/properties/audio/codec.py | 5 +- knowit/properties/subtitle/format.py | 2 +- knowit/properties/video/codec.py | 2 +- knowit/properties/video/profile.py | 7 +- knowit/properties/video/ratio.py | 3 +- knowit/property.py | 66 ++++++++++---- 11 files changed, 184 insertions(+), 88 deletions(-) diff --git a/knowit/__init__.py b/knowit/__init__.py index f07ab54..d477449 100644 --- a/knowit/__init__.py +++ b/knowit/__init__.py @@ -1,5 +1,4 @@ """Know your media files better.""" - __title__ = 'knowit' __version__ = '0.3.0' __short_version__ = '.'.join(__version__.split('.')[:2]) diff --git a/knowit/__main__.py b/knowit/__main__.py index 026b001..fb3c366 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -1,7 +1,9 @@ - +import argparse import json import logging +import os import sys +import typing from argparse import ArgumentParser import yaml @@ -26,45 +28,95 @@ logger = logging.getLogger('knowit') -def build_argument_parser(): - """Build the argument parser. - - :return: the argument parser - :rtype: ArgumentParser - """ +def build_argument_parser() -> ArgumentParser: + """Build the argument parser.""" opts = ArgumentParser() - opts.add_argument(dest='videopath', help='Path to the video to introspect', nargs='*') + opts.add_argument( + dest='videopath', + help='Path to the video to introspect', + nargs='*', + type=str, + ) provider_opts = opts.add_argument_group('Providers') - provider_opts.add_argument('-p', '--provider', dest='provider', - help='The provider to be used: mediainfo, ffmpeg or enzyme.') + provider_opts.add_argument( + '-p', + '--provider', + dest='provider', + help='The provider to be used: mediainfo, ffmpeg or enzyme.', + type=str, + ) output_opts = opts.add_argument_group('Output') - output_opts.add_argument('--debug', action='store_true', dest='debug', - help='Print useful information for debugging knowit and for reporting bugs.') - output_opts.add_argument('--report', action='store_true', dest='report', - help='Parse media and report all non-detected values') - output_opts.add_argument('-y', '--yaml', action='store_true', dest='yaml', - help='Display output in yaml format') - output_opts.add_argument('-N', '--no-units', action='store_true', dest='no_units', - help='Display output without units') - output_opts.add_argument('-P', '--profile', dest='profile', - help='Display values according to specified profile: code, default, human, technical') + output_opts.add_argument( + '--debug', + action='store_true', + dest='debug', + help='Print information for debugging knowit and for reporting bugs.', + type=bool, + ) + output_opts.add_argument( + '--report', + action='store_true', + dest='report', + help='Parse media and report all non-detected values', + type=bool, + ) + output_opts.add_argument( + '-y', + '--yaml', + action='store_true', + dest='yaml', + help='Display output in yaml format', + type=bool, + ) + output_opts.add_argument( + '-N', + '--no-units', + action='store_true', + dest='no_units', + help='Display output without units', + type=bool, + ) + output_opts.add_argument( + '-P', + '--profile', + dest='profile', + help='Display values according to specified profile: code, default, human, technical', + type=str, + ) conf_opts = opts.add_argument_group('Configuration') - conf_opts.add_argument('--mediainfo', dest='mediainfo', - help='The location to search for MediaInfo binaries') - conf_opts.add_argument('--ffmpeg', dest='ffmpeg', - help='The location to search for FFmpeg (ffprobe) binaries') + conf_opts.add_argument( + '--mediainfo', + dest='mediainfo', + help='The location to search for MediaInfo binaries', + type=str, + ) + conf_opts.add_argument( + '--ffmpeg', + dest='ffmpeg', + help='The location to search for FFmpeg (ffprobe) binaries', + type=str, + ) information_opts = opts.add_argument_group('Information') - information_opts.add_argument('--version', dest='version', action='store_true', - help='Display knowit version.') + information_opts.add_argument( + '--version', + dest='version', + action='store_true', + help='Display knowit version.', + type=bool, + ) return opts -def knowit(video_path, options, context): +def knowit( + video_path: typing.Union[str, os.PathLike], + options: argparse.Namespace, + context: typing.MutableMapping, +) -> typing.Mapping: """Extract video metadata.""" context['path'] = video_path if not options.report: @@ -75,23 +127,34 @@ def knowit(video_path, options, context): if not options.report: console.info('Knowit %s found: ', __version__) console.info(dump(info, options, context)) - return info -def dump(info, options, context): +def dump( + info: typing.Mapping[str, typing.Any], + options: argparse.Namespace, + context: typing.Mapping, +) -> str: """Convert info to string using json or yaml format.""" if options.yaml: data = {info['path']: info} if 'path' in info else info - result = yaml.dump(data, Dumper=get_yaml_dumper(context), - default_flow_style=False, allow_unicode=True) + result = yaml.dump( + data, + Dumper=get_yaml_dumper(context), + default_flow_style=False, + allow_unicode=True, + ) else: - result = json.dumps(info, cls=get_json_encoder(context), indent=4, ensure_ascii=False) - + result = json.dumps( + info, + cls=get_json_encoder(context), + indent=4, + ensure_ascii=False, + ) return result -def main(args=None): +def main(args: 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/knowit/api.py b/knowit/api.py index b2a9821..396f2b3 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -1,4 +1,4 @@ - +import os import traceback import typing @@ -26,7 +26,7 @@ class KnowitException(Exception): """Exception raised when knowit fails to perform media info extraction because of an internal error.""" -def initialize(context=None): +def initialize(context: typing.Optional[typing.Mapping] = None) -> None: """Initialize knowit.""" if not available_providers: context = context or {} @@ -35,21 +35,12 @@ def initialize(context=None): available_providers[name] = provider_cls(config, context.get(name) or config.general.get(name)) -def know(video_path, context=None): - """Return a dict containing the video metadata. - - :param video_path: - :type video_path: string - :param context: - :type context: dict - :return: - :rtype: dict - """ - try: - # handle path-like objects - video_path = video_path.__fspath__() - except AttributeError: - pass +def know( + video_path: typing.Union[str, os.PathLike], + context: typing.Optional[typing.MutableMapping] = None +) -> typing.Mapping: + """Return a mapping of video metadata.""" + video_path = os.fspath(video_path) try: context = context or {} @@ -70,7 +61,7 @@ def know(video_path, context=None): raise KnowitException(debug_info(context=context, exc_info=True)) -def dependencies(context=None): +def dependencies(context: typing.Mapping = None) -> typing.Mapping: """Return all dependencies detected by knowit.""" deps = {} try: @@ -86,12 +77,15 @@ def dependencies(context=None): return deps -def _centered(value): +def _centered(value: str) -> str: value = value[-52:] return f'| {value:^53} |' -def debug_info(context=None, exc_info=False): +def debug_info( + context: typing.Optional[typing.MutableMapping] = None, + exc_info: bool = False, +) -> str: lines = [ '+-------------------------------------------------------+', _centered(f'KnowIt {__version__}'), diff --git a/knowit/config.py b/knowit/config.py index be3ae12..349922d 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -1,4 +1,4 @@ - +import os import typing from logging import NullHandler, getLogger @@ -25,7 +25,7 @@ class Config: """Application config class.""" @classmethod - def build(cls, path=None): + def build(cls, path: typing.Optional[typing.Union[str, os.PathLike]] = None) -> 'Config': """Build config instance.""" loader = get_yaml_loader() with resource_stream('knowit', 'defaults.yml') as stream: diff --git a/knowit/core.py b/knowit/core.py index 88c7a66..aa3cfe4 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -1,25 +1,32 @@ - +import typing from logging import NullHandler, getLogger logger = getLogger(__name__) logger.addHandler(NullHandler()) +T = typing.TypeVar('T') + -class Reportable: +class Reportable(typing.Generic[T]): """Reportable abstract class.""" - def __init__(self, name, description=None, reportable=True): + def __init__( + self, + name: str, + description: typing.Optional[str] = None, + reportable: bool = True, + ): """Initialize the object.""" self.name = name self._description = description self.reportable = reportable @property - def description(self): + def description(self) -> str: """Rule description.""" return self._description or self.name - def report(self, value, context): + def report(self, value: T, context: typing.MutableMapping) -> None: """Report unknown value.""" if not value or not self.reportable: return diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index df3ed46..c5045b6 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -1,3 +1,4 @@ +import typing from knowit.property import Configurable @@ -6,7 +7,7 @@ class AudioCodec(Configurable): """Audio codec property.""" @classmethod - def _extract_key(cls, value): + def _extract_key(cls, value) -> str: key = str(value).upper() if key.startswith('A_'): key = key[2:] @@ -15,6 +16,6 @@ def _extract_key(cls, value): return key.split(' ')[0] @classmethod - def _extract_fallback_key(cls, value, key): + def _extract_fallback_key(cls, value, key) -> typing.Optional[str]: if '/' in key: return key.split('/')[0] diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle/format.py index 088473d..d59c3fb 100644 --- a/knowit/properties/subtitle/format.py +++ b/knowit/properties/subtitle/format.py @@ -6,7 +6,7 @@ class SubtitleFormat(Configurable): """Subtitle Format property.""" @classmethod - def _extract_key(cls, value): + def _extract_key(cls, value) -> str: key = str(value).upper() if key.startswith('S_'): key = key[2:] diff --git a/knowit/properties/video/codec.py b/knowit/properties/video/codec.py index abd2f12..7b33b3f 100644 --- a/knowit/properties/video/codec.py +++ b/knowit/properties/video/codec.py @@ -6,7 +6,7 @@ class VideoCodec(Configurable): """Video Codec handler.""" @classmethod - def _extract_key(cls, value): + def _extract_key(cls, value) -> str: key = value.upper().split('/')[-1] if key.startswith('V_'): key = key[2:] diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py index 34f6acd..55cc3fe 100644 --- a/knowit/properties/video/profile.py +++ b/knowit/properties/video/profile.py @@ -1,3 +1,4 @@ +import typing from knowit.property import Configurable @@ -6,7 +7,7 @@ class VideoProfile(Configurable): """Video Profile property.""" @classmethod - def _extract_key(cls, value): + def _extract_key(cls, value) -> str: return value.upper().split('@')[0] @@ -14,7 +15,7 @@ class VideoProfileLevel(Configurable): """Video Profile Level property.""" @classmethod - def _extract_key(cls, value): + def _extract_key(cls, value) -> typing.Union[str, bool]: values = str(value).upper().split('@') if len(values) > 1: value = values[1] @@ -28,7 +29,7 @@ class VideoProfileTier(Configurable): """Video Profile Tier property.""" @classmethod - def _extract_key(cls, value): + def _extract_key(cls, value) -> typing.Union[str, bool]: values = str(value).upper().split('@') if len(values) > 2: return values[2] diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index 5946e6c..e8c3fba 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -1,5 +1,6 @@ import re +import typing from knowit.property import Property @@ -14,7 +15,7 @@ def __init__(self, name, unit=None, **kwargs): ratio_re = re.compile(r'(?P\d+)[:/](?P\d+)') - def handle(self, value, context): + def handle(self, value, context) -> typing.Optional[float]: """Handle ratio.""" match = self.ratio_re.match(value) if match: diff --git a/knowit/property.py b/knowit/property.py index 5c62f36..ce9b5cd 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -1,4 +1,4 @@ - +import typing from logging import NullHandler, getLogger from knowit.core import Reportable @@ -9,14 +9,25 @@ _visible_chars_table = dict.fromkeys(range(32)) -def _is_unknown(value): +def _is_unknown(value: typing.Any) -> bool: return isinstance(value, str) and (not value or value.lower() == 'unknown') -class Property(Reportable): +T = typing.TypeVar('T') + + +class Property(Reportable[T]): """Property class.""" - def __init__(self, name, default=None, private=False, description=None, delimiter=' / ', **kwargs): + def __init__( + self, + name: str, + default: typing.Optional[T] = None, + private: bool = False, + description: typing.Optional[str] = None, + delimiter: str = ' / ', + **kwargs, + ): """Init method.""" super().__init__(name, description, **kwargs) self.default = default @@ -24,7 +35,11 @@ def __init__(self, name, default=None, private=False, description=None, delimite # Used to detect duplicated values. e.g.: en / en or High@L4.0 / High@L4.0 or Progressive / Progressive self.delimiter = delimiter - def extract_value(self, track, context): + def extract_value( + self, + track: typing.Mapping, + context: typing.MutableMapping, + ) -> typing.Optional[T]: """Extract the property value from a given track.""" names = self.name.split('.') value = track.get(names[0], {}).get(names[1]) if len(names) == 2 else track.get(self.name) @@ -44,42 +59,49 @@ def extract_value(self, track, context): value = self._deduplicate(value) result = self.handle(value, context) - if result is not None and not _is_unknown(result): + if not _is_unknown(result): return result + else: + return None @classmethod - def _deduplicate(cls, value): + def _deduplicate(cls, value: str) -> str: values = value.split(' / ') if len(values) == 2 and values[0] == values[1]: return values[0] return value - def handle(self, value, context): + def handle(self, value: T, context: typing.MutableMapping) -> T: """Return the value without any modification.""" return value -class Configurable(Property): +class Configurable(Property[T]): """Configurable property where values are in a config mapping.""" - def __init__(self, config, *args, **kwargs): + def __init__(self, config: typing.Mapping[str, typing.Mapping], *args, **kwargs): """Init method.""" super().__init__(*args, **kwargs) self.mapping = getattr(config, self.__class__.__name__) @classmethod - def _extract_key(cls, value): + def _extract_key(cls, value: str) -> typing.Union[str, bool]: return value.upper() @classmethod - def _extract_fallback_key(cls, value, key): - pass - - def _lookup(self, key, context): + def _extract_fallback_key(cls, value: str, key: str) -> typing.Optional[T]: + return None + + def _lookup( + self, + key: str, + context: typing.MutableMapping, + ) -> typing.Union[T, None, bool]: result = self.mapping.get(key) if result is not None: result = getattr(result, context.get('profile') or 'default') return result if result != '__ignored__' else False + return None def handle(self, value, context): """Return Variable or Constant.""" @@ -114,7 +136,11 @@ def __init__(self, prop=None, delimiter='/', single=False, handler=None, name=No self.single = single self.handler = handler - def handle(self, value, context): + def handle( + self, + value: str, + context: typing.Mapping, + ) -> typing.Union[T, typing.List[T]]: """Handle properties with multiple values.""" values = (self._split(value[0], self.delimiter) if len(value) == 1 else value) if isinstance(value, list) else self._split(value, self.delimiter) @@ -125,8 +151,12 @@ def handle(self, value, context): return call(values[0], context) @classmethod - def _split(cls, value, delimiter='/'): + def _split( + cls, + value: typing.Optional[T], + delimiter: str = '/', + ) -> typing.Optional[typing.List[str]]: if value is None: - return + return None return [x.strip() for x in str(value).split(delimiter)] From 206619d60c7b5dfbe42bc6f0e2fbd660350a8734 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 20:25:10 -0400 Subject: [PATCH 023/102] Fix missing return --- knowit/properties/audio/codec.py | 2 ++ knowit/properties/video/ratio.py | 1 + 2 files changed, 3 insertions(+) diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index c5045b6..297ebc8 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -19,3 +19,5 @@ def _extract_key(cls, value) -> str: def _extract_fallback_key(cls, value, key) -> typing.Optional[str]: if '/' in key: return key.split('/')[0] + else: + return None diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index e8c3fba..9c40719 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -30,3 +30,4 @@ def handle(self, value, context) -> typing.Optional[float]: return result self.report(value, context) + return None From 6a9419740e325b0b14a510439fd52d7924228955 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 20:51:49 -0400 Subject: [PATCH 024/102] Fix return value expected --- knowit/property.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/knowit/property.py b/knowit/property.py index ce9b5cd..e2d10ca 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -45,7 +45,7 @@ def extract_value( value = track.get(names[0], {}).get(names[1]) if len(names) == 2 else track.get(self.name) if value is None: if self.default is None: - return + return None value = self.default @@ -55,7 +55,7 @@ def extract_value( if isinstance(value, str): value = value.translate(_visible_chars_table).strip() if _is_unknown(value): - return + return None value = self._deduplicate(value) result = self.handle(value, context) From 4a456cdaa41a9f25d29e9f8cb931de41edea4ca8 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 22 Mar 2021 07:51:05 -0400 Subject: [PATCH 025/102] Fix KnowitException docstring by making it more generalized. --- knowit/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/api.py b/knowit/api.py index 396f2b3..65b8822 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -23,7 +23,7 @@ class KnowitException(Exception): - """Exception raised when knowit fails to perform media info extraction because of an internal error.""" + """Exception raised when knowit encounters an internal error.""" def initialize(context: typing.Optional[typing.Mapping] = None) -> None: From d5ae9bce4d1a333878325c0604a473ef98d754e4 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sun, 21 Mar 2021 20:24:36 -0400 Subject: [PATCH 026/102] Fix __future__ import of annotations not available on Python 3.6 --- knowit/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index 31b959a..ebd2c3c 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import os import sys import typing From c1506d04112e215412eea669ed2c8595314f23a8 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 22 Mar 2021 08:07:07 -0400 Subject: [PATCH 027/102] Fix passing text to yaml.load instead of bytes --- knowit/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/config.py b/knowit/config.py index 349922d..43b3491 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -32,7 +32,7 @@ def build(cls, path: typing.Optional[typing.Union[str, os.PathLike]] = None) -> cfgs = [yaml.load(stream, Loader=loader)] if path: - with open(path, 'r') as stream: + with open(path, 'rb') as stream: cfgs.append(yaml.load(stream, Loader=loader)) profiles_data = {} From aa604933ee97bbd63dacba4d85b6572a3b3a3327 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 22 Mar 2021 08:41:55 -0400 Subject: [PATCH 028/102] Refactor code to make intent clearer --- knowit/property.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/knowit/property.py b/knowit/property.py index e2d10ca..d6efc42 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -142,12 +142,18 @@ def handle( context: typing.Mapping, ) -> typing.Union[T, typing.List[T]]: """Handle properties with multiple values.""" - values = (self._split(value[0], self.delimiter) - if len(value) == 1 else value) if isinstance(value, list) else self._split(value, self.delimiter) + if isinstance(value, list): + if len(value) == 1: + values = self._split(value[0], self.delimiter) + else: + values = value + else: + values = self._split(value, self.delimiter) call = self.handler or self.prop.handle + if values is None: + return call(values, context) if len(values) > 1 and not self.single: return [call(item, context) if not _is_unknown(item) else None for item in values] - return call(values[0], context) @classmethod From 45d659b7b745ca96a23e0e9c6e23cc445270069e Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 22 Mar 2021 09:33:28 -0400 Subject: [PATCH 029/102] Fix 'Config' has no attribute 'general' --- knowit/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/knowit/api.py b/knowit/api.py index 65b8822..886e043 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -32,7 +32,9 @@ def initialize(context: typing.Optional[typing.Mapping] = None) -> None: context = context or {} config = Config.build(context.get('config')) for name, provider_cls in _provider_map.items(): - available_providers[name] = provider_cls(config, context.get(name) or config.general.get(name)) + general_config = getattr(config, 'general', {}) + mapping = context.get(name) or general_config.get(name) + available_providers[name] = provider_cls(config, mapping) def know( From a139e8909597faf1e87b89c829dabcd315c44992 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 22 Mar 2021 09:33:45 -0400 Subject: [PATCH 030/102] Add type-hints --- knowit/__main__.py | 2 +- knowit/config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/knowit/__main__.py b/knowit/__main__.py index fb3c366..a3fe99c 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -169,7 +169,7 @@ def main(args: typing.List[str] = None) -> None: paths = recurse_paths(options.videopath) if paths: - report = {} + report: typing.MutableMapping[str, str] = {} for i, videopath in enumerate(paths): try: context = dict(vars(options)) diff --git a/knowit/config.py b/knowit/config.py index 43b3491..8331399 100644 --- a/knowit/config.py +++ b/knowit/config.py @@ -45,7 +45,7 @@ def build(cls, path: typing.Optional[typing.Union[str, os.PathLike]] = None) -> if 'knowledge' in cfg: knowledge_data.update(cfg['knowledge']) - data = {'general': {}} + data: typing.Dict[str, typing.MutableMapping] = {'general': {}} for class_name, data_map in knowledge_data.items(): data.setdefault(class_name, {}) for code, detection_values in data_map.items(): From 8025a79fc5204f097b605eb8c0fe1c420cf5f820 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 22 Mar 2021 09:46:53 -0400 Subject: [PATCH 031/102] Add linting and type-checks to CI --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2d6cc17..cbaa71b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,3 +34,7 @@ install: - pip install -e . script: tox + +after_script: + - tox -e lint + - tox -e type-check From 59d38d6c5e1ee1156883c316456e3f9d53f403fe Mon Sep 17 00:00:00 2001 From: Rato Date: Mon, 22 Mar 2021 22:22:16 +0100 Subject: [PATCH 032/102] Add Dockerfile --- Dockerfile | 17 +++++++++++++++++ README.rst | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e0f3e2d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.9-slim + +RUN apt-get update \ + && apt-get install -y mediainfo ffmpeg \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /usr/src/app + +COPY . /usr/src/app +RUN cd /usr/src/app \ + && pip install --no-cache-dir -r requirements.txt + +WORKDIR / + +ENTRYPOINT ["knowit"] +CMD ["--help"] diff --git a/README.rst b/README.rst index 097eda0..678024b 100644 --- a/README.rst +++ b/README.rst @@ -122,6 +122,60 @@ Extract information from a video file using ffprobe:: } +Using docker:: + + docker run -it --rm -v /folder:/folder knowit /folder/Audio Samples/hd_dtsma_7.1.mkv + For: /folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv + Knowit 0.2.4 found: + { + "title": "7.1Ch DTS-HD MA - Speaker Mapping Test File", + "path": "/folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "duration": "0:01:37", + "size": "40.77 MB", + "bit_rate": "3.3 Mbps", + "container": "mkv", + "video": [ + { + "id": 1, + "duration": "0:01:37", + "width": "1920 pixel", + "height": "1080 pixel", + "scan_type": "Progressive", + "aspect_ratio": 1.778, + "pixel_aspect_ratio": 1.0, + "resolution": "1080p", + "frame_rate": "23.976 FPS", + "bit_depth": "8 bit", + "codec": "H.264", + "profile": "Main", + "profile_level": "4", + "media_type": "video/H264", + "default": true, + "language": "Undetermined" + } + ], + "audio": [ + { + "id": 2, + "name": "7.1Ch DTS-HD MA", + "language": "English", + "duration": "0:01:37", + "codec": "DTS-HD", + "profile": "Master Audio", + "channels_count": 8, + "channels": "7.1", + "bit_depth": "24 bit", + "bit_rate_mode": "Variable", + "sampling_rate": "48.0 KHz", + "compression": "Lossless", + "default": true + } + ], + "provider": "libmediainfo.so.0" + } + + + All available CLI options:: $ knowit --help From da3b35aa7e62a6455efeb9408e90afce15520a09 Mon Sep 17 00:00:00 2001 From: Rato Date: Mon, 22 Mar 2021 22:25:19 +0100 Subject: [PATCH 033/102] Back to dev version. Fixes TypeError: __init__() got an unexpected keyword argument 'type' --- knowit/__init__.py | 2 +- knowit/__main__.py | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/knowit/__init__.py b/knowit/__init__.py index d477449..14dfc5e 100644 --- a/knowit/__init__.py +++ b/knowit/__init__.py @@ -1,6 +1,6 @@ """Know your media files better.""" __title__ = 'knowit' -__version__ = '0.3.0' +__version__ = '0.4.0-dev' __short_version__ = '.'.join(__version__.split('.')[:2]) __author__ = 'Rato AQ2' __license__ = 'MIT' diff --git a/knowit/__main__.py b/knowit/__main__.py index a3fe99c..d08532d 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -52,31 +52,27 @@ def build_argument_parser() -> ArgumentParser: '--debug', action='store_true', dest='debug', - help='Print information for debugging knowit and for reporting bugs.', - type=bool, + help='Print information for debugging knowit and for reporting bugs.' ) output_opts.add_argument( '--report', action='store_true', dest='report', - help='Parse media and report all non-detected values', - type=bool, + help='Parse media and report all non-detected values' ) output_opts.add_argument( '-y', '--yaml', action='store_true', dest='yaml', - help='Display output in yaml format', - type=bool, + help='Display output in yaml format' ) output_opts.add_argument( '-N', '--no-units', action='store_true', dest='no_units', - help='Display output without units', - type=bool, + help='Display output without units' ) output_opts.add_argument( '-P', @@ -105,8 +101,7 @@ def build_argument_parser() -> ArgumentParser: '--version', dest='version', action='store_true', - help='Display knowit version.', - type=bool, + help='Display knowit version.' ) return opts From 56db735a3e58b244088cf999fd94ba744c3b8250 Mon Sep 17 00:00:00 2001 From: Rato Date: Mon, 22 Mar 2021 22:28:19 +0100 Subject: [PATCH 034/102] Move MediaInfo to json output --- knowit/defaults.yml | 1 + knowit/providers/mediainfo.py | 110 +++++++++++++++++----------------- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/knowit/defaults.yml b/knowit/defaults.yml index 234f934..11d1fa9 100644 --- a/knowit/defaults.yml +++ b/knowit/defaults.yml @@ -282,6 +282,7 @@ knowledge: - DTS-HD AAC: - AAC + - AAC-2 FLAC: - FLAC PCM: diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 2789e43..881b2d2 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -1,10 +1,9 @@ +import json import re from ctypes import c_void_p, c_wchar_p from logging import DEBUG, NullHandler, getLogger from subprocess import CalledProcessError, check_output -from xml.dom import minidom -from xml.etree import ElementTree from pymediainfo import MediaInfo from pymediainfo import __version__ as pymediainfo_version @@ -124,8 +123,7 @@ class MediaInfoCliExecutor(MediaInfoExecutor): } def _execute(self, filename): - output_type = 'OLDXML' if self.version >= (17, 10) else 'XML' - return MediaInfo(check_output([self.location, '--Output=' + output_type, '--Full', filename]).decode()) + return MediaInfo(check_output([self.location, '--Output=JSON', '--Full', filename]).decode()) @classmethod def create(cls, os_family=None, suggested_path=None): @@ -158,7 +156,7 @@ class MediaInfoCTypesExecutor(MediaInfoExecutor): def _execute(self, filename): # Create a MediaInfo handle - return MediaInfo.parse(filename, library_file=self.location) + return MediaInfo.parse(filename, library_file=self.location, output='JSON') @classmethod def create(cls, os_family=None, suggested_path=None): @@ -183,69 +181,69 @@ def __init__(self, config, suggested_path): """Init method.""" super().__init__(config, { 'general': { - 'title': Property('title', description='media title'), - 'path': Property('complete_name', description='media path'), - 'duration': Duration('duration', description='media duration'), - 'size': Quantity('file_size', units.byte, description='media size'), - 'bit_rate': Quantity('overall_bit_rate', units.bps, description='media bit rate'), + 'title': Property('Title', description='media title'), + 'path': Property('CompleteName', description='media path'), + 'duration': Duration('Duration', description='media duration'), + 'size': Quantity('FileSize', units.byte, description='media size'), + 'bit_rate': Quantity('OverallBitRate', units.bps, description='media bit rate'), }, 'video': { - 'id': Basic('track_id', int, allow_fallback=True, description='video track number'), + 'id': Basic('ID', int, allow_fallback=True, description='video track number'), 'name': Property('name', description='video track name'), - 'language': Language('language', description='video language'), - 'duration': Duration('duration', description='video duration'), - 'size': Quantity('stream_size', units.byte, description='video stream size'), - 'width': Quantity('width', units.pixel), - 'height': Quantity('height', units.pixel), - 'scan_type': ScanType(config, 'scan_type', default='Progressive', description='video scan type'), - 'aspect_ratio': Basic('display_aspect_ratio', float, description='display aspect ratio'), - 'pixel_aspect_ratio': Basic('pixel_aspect_ratio', float, description='pixel aspect ratio'), + 'language': Language('Language', description='video language'), + 'duration': Duration('Duration', description='video duration'), + 'size': Quantity('StreamSize', units.byte, description='video stream size'), + 'width': Quantity('Width', units.pixel), + 'height': Quantity('Height', units.pixel), + 'scan_type': ScanType(config, 'ScanType', default='Progressive', description='video scan type'), + 'aspect_ratio': Basic('DisplayAspectRatio', float, description='display aspect ratio'), + 'pixel_aspect_ratio': Basic('PixelAspectRatio', float, description='pixel aspect ratio'), 'resolution': None, # populated with ResolutionRule - 'frame_rate': Quantity('frame_rate', units.FPS, float, description='video frame rate'), + 'frame_rate': Quantity('FrameRate', units.FPS, float, description='video frame rate'), # frame_rate_mode - 'bit_rate': Quantity('bit_rate', units.bps, description='video bit rate'), - 'bit_depth': Quantity('bit_depth', units.bit, description='video bit depth'), - 'codec': VideoCodec(config, 'codec', description='video codec'), - 'profile': VideoProfile(config, 'codec_profile', description='video codec profile'), - 'profile_level': VideoProfileLevel(config, 'codec_profile', description='video codec profile level'), - 'profile_tier': VideoProfileTier(config, 'codec_profile', description='video codec profile tier'), + 'bit_rate': Quantity('BitRate', units.bps, description='video bit rate'), + 'bit_depth': Quantity('BitDepth', units.bit, description='video bit depth'), + 'codec': VideoCodec(config, 'CodecID', description='video codec'), + 'profile': VideoProfile(config, 'Format_Profile', description='video codec profile'), + 'profile_level': VideoProfileLevel(config, 'Format_Level', description='video codec profile level'), + 'profile_tier': VideoProfileTier(config, 'Format_Profile', description='video codec profile tier'), 'encoder': VideoEncoder(config, 'encoded_library_name', description='video encoder'), - 'media_type': Property('internet_media_type', description='video media type'), - 'forced': YesNo('forced', hide_value=False, description='video track forced'), - 'default': YesNo('default', hide_value=False, description='video track default'), + 'media_type': Property('InternetMediaType', description='video media type'), + 'forced': YesNo('Forced', hide_value=False, description='video track forced'), + 'default': YesNo('Default', hide_value=False, description='video track default'), }, 'audio': { - 'id': Basic('track_id', int, allow_fallback=True, description='audio track number'), - 'name': Property('title', description='audio track name'), - 'language': Language('language', description='audio language'), - 'duration': Duration('duration', description='audio duration'), - 'size': Quantity('stream_size', units.byte, description='audio stream size'), - 'codec': MultiValue(AudioCodec(config, 'codec', description='audio codec')), - 'profile': MultiValue(AudioProfile(config, 'format_profile', description='audio codec profile'), + 'id': Basic('ID', int, allow_fallback=True, description='audio track number'), + 'name': Property('Title', description='audio track name'), + 'language': Language('Language', description='audio language'), + 'duration': Duration('Duration', description='audio duration'), + 'size': Quantity('StreamSize', units.byte, description='audio stream size'), + 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), + 'profile': MultiValue(AudioProfile(config, 'Format_Profile', description='audio codec profile'), delimiter=' / '), - 'channels_count': MultiValue(AudioChannels('channel_s', description='audio channels count')), + 'channels_count': MultiValue(AudioChannels('Channels', description='audio channels count')), 'channel_positions': MultiValue(name='other_channel_positions', handler=(lambda x, *args: x), delimiter=' / ', private=True, description='audio channels position'), 'channels': None, # populated with AudioChannelsRule - 'bit_depth': Quantity('bit_depth', units.bit, description='audio bit depth'), - 'bit_rate': MultiValue(Quantity('bit_rate', units.bps, description='audio bit rate')), - 'bit_rate_mode': MultiValue(BitRateMode(config, 'bit_rate_mode', description='audio bit rate mode')), - 'sampling_rate': MultiValue(Quantity('sampling_rate', units.Hz, description='audio sampling rate')), - 'compression': MultiValue(AudioCompression(config, 'compression_mode', + 'bit_depth': Quantity('BitDepth', units.bit, description='audio bit depth'), + 'bit_rate': MultiValue(Quantity('BitRate', units.bps, description='audio bit rate')), + 'bit_rate_mode': MultiValue(BitRateMode(config, 'BitRateMode', description='audio bit rate mode')), + 'sampling_rate': MultiValue(Quantity('SamplingRate', units.Hz, description='audio sampling rate')), + 'compression': MultiValue(AudioCompression(config, 'Compression_Mode', description='audio compression')), - 'forced': YesNo('forced', hide_value=False, description='audio track forced'), - 'default': YesNo('default', hide_value=False, description='audio track default'), + 'forced': YesNo('Forced', hide_value=False, description='audio track forced'), + 'default': YesNo('Default', hide_value=False, description='audio track default'), }, 'subtitle': { - 'id': Basic('track_id', int, allow_fallback=True, description='subtitle track number'), - 'name': Property('title', description='subtitle track name'), - 'language': Language('language', description='subtitle language'), + 'id': Basic('ID', int, allow_fallback=True, description='subtitle track number'), + 'name': Property('Title', description='subtitle track name'), + 'language': Language('Language', description='subtitle language'), 'hearing_impaired': None, # populated with HearingImpairedRule '_closed_caption': Property('captionservicename', private=True), 'closed_caption': None, # populated with ClosedCaptionRule - 'format': SubtitleFormat(config, 'codec_id', description='subtitle format'), - 'forced': YesNo('forced', hide_value=False, description='subtitle track forced'), - 'default': YesNo('default', hide_value=False, description='subtitle track default'), + 'format': SubtitleFormat(config, 'CodecID', description='subtitle format'), + 'forced': YesNo('Forced', hide_value=False, description='subtitle track forced'), + 'default': YesNo('Default', hide_value=False, description='subtitle track default'), }, }, { 'video': { @@ -277,11 +275,11 @@ def accepts(self, video_path): def describe(self, video_path, context): """Return video metadata.""" media_info = self.executor.extract_info(video_path) + data = json.loads(media_info) def debug_data(): """Debug data.""" - xml = ElementTree.tostring(media_info.xml_dom).decode().replace('\r', '').replace('\n', '') - return minidom.parseString(xml).toprettyxml(indent=' ', newl='\n', encoding='utf-8').decode() + return json.dumps(data, indent=4) context['debug_data'] = debug_data @@ -289,15 +287,15 @@ def debug_data(): logger.debug('Video %r scanned using mediainfo %r has raw data:\n%s', video_path, self.executor.location, debug_data()) - data = media_info.to_data() result = {} - if data.get('tracks'): + tracks = data.get('media', {}).get('track', []) + if tracks: general_tracks = [] video_tracks = [] audio_tracks = [] subtitle_tracks = [] - for track in data.get('tracks'): - track_type = track.get('track_type') + for track in tracks: + track_type = track.get('@type') if track_type == 'General': general_tracks.append(track) elif track_type == 'Video': From 51160e3e54fa5fee8f746ea454ff1b2b3ae71ad7 Mon Sep 17 00:00:00 2001 From: Rato Date: Tue, 23 Mar 2021 19:47:45 +0100 Subject: [PATCH 035/102] Fixes mediainfo failing tests --- knowit/defaults.yml | 1 + knowit/providers/mediainfo.py | 30 +-- knowit/rules/__init__.py | 1 + knowit/rules/audio/__init__.py | 1 + knowit/rules/audio/profile.py | 11 + tests/__init__.py | 2 +- tests/conftest.py | 2 +- ...s-hd-ma-speaker-mapping-test-file.mkv.json | 207 +++++++++++++++++ ...ts-hd-ma-speaker-mapping-test-file.mkv.xml | 215 ------------------ tests/test_mediainfo.py | 2 +- 10 files changed, 241 insertions(+), 231 deletions(-) create mode 100644 knowit/rules/audio/profile.py create mode 100644 tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json delete mode 100644 tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.xml diff --git a/knowit/defaults.yml b/knowit/defaults.yml index 11d1fa9..e218323 100644 --- a/knowit/defaults.yml +++ b/knowit/defaults.yml @@ -232,6 +232,7 @@ knowledge: MA: - MA - DTS-HD MA + - XLL MAIN: - MAIN LC: diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 881b2d2..4890d49 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -24,7 +24,6 @@ VideoCodec, VideoEncoder, VideoProfile, - VideoProfileLevel, VideoProfileTier, YesNo, ) @@ -39,6 +38,7 @@ from ..rules import ( AtmosRule, AudioChannelsRule, + AudioProfileRule, ClosedCaptionRule, DtsHdRule, HearingImpairedRule, @@ -123,7 +123,7 @@ class MediaInfoCliExecutor(MediaInfoExecutor): } def _execute(self, filename): - return MediaInfo(check_output([self.location, '--Output=JSON', '--Full', filename]).decode()) + return json.loads(check_output([self.location, '--Output=JSON', '--Full', filename]).decode()) @classmethod def create(cls, os_family=None, suggested_path=None): @@ -156,7 +156,7 @@ class MediaInfoCTypesExecutor(MediaInfoExecutor): def _execute(self, filename): # Create a MediaInfo handle - return MediaInfo.parse(filename, library_file=self.location, output='JSON') + return json.loads(MediaInfo.parse(filename, library_file=self.location, output='JSON')) @classmethod def create(cls, os_family=None, suggested_path=None): @@ -189,7 +189,7 @@ def __init__(self, config, suggested_path): }, 'video': { 'id': Basic('ID', int, allow_fallback=True, description='video track number'), - 'name': Property('name', description='video track name'), + 'name': Property('Title', description='video track name'), 'language': Language('Language', description='video language'), 'duration': Duration('Duration', description='video duration'), 'size': Quantity('StreamSize', units.byte, description='video stream size'), @@ -205,9 +205,9 @@ def __init__(self, config, suggested_path): 'bit_depth': Quantity('BitDepth', units.bit, description='video bit depth'), 'codec': VideoCodec(config, 'CodecID', description='video codec'), 'profile': VideoProfile(config, 'Format_Profile', description='video codec profile'), - 'profile_level': VideoProfileLevel(config, 'Format_Level', description='video codec profile level'), - 'profile_tier': VideoProfileTier(config, 'Format_Profile', description='video codec profile tier'), - 'encoder': VideoEncoder(config, 'encoded_library_name', description='video encoder'), + 'profile_level': Property('Format_Level', description='video codec profile level'), + 'profile_tier': VideoProfileTier(config, 'Format_Tier', description='video codec profile tier'), + 'encoder': VideoEncoder(config, 'Encoded_Library_Name', description='video encoder'), 'media_type': Property('InternetMediaType', description='video media type'), 'forced': YesNo('Forced', hide_value=False, description='video track forced'), 'default': YesNo('Default', hide_value=False, description='video track default'), @@ -219,15 +219,19 @@ def __init__(self, config, suggested_path): 'duration': Duration('Duration', description='audio duration'), 'size': Quantity('StreamSize', units.byte, description='audio stream size'), 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), - 'profile': MultiValue(AudioProfile(config, 'Format_Profile', description='audio codec profile'), + 'profile': MultiValue(AudioProfile(config, 'Format_Profile', + description='audio codec profile'), delimiter=' / '), + '_profile': MultiValue(AudioProfile(config, 'Format_AdditionalFeatures', + description='audio codec additional features'), + delimiter=' / ', private=True), 'channels_count': MultiValue(AudioChannels('Channels', description='audio channels count')), - 'channel_positions': MultiValue(name='other_channel_positions', handler=(lambda x, *args: x), + 'channel_positions': MultiValue(name='ChannelPositions_String2', handler=(lambda x, *args: x), delimiter=' / ', private=True, description='audio channels position'), 'channels': None, # populated with AudioChannelsRule 'bit_depth': Quantity('BitDepth', units.bit, description='audio bit depth'), 'bit_rate': MultiValue(Quantity('BitRate', units.bps, description='audio bit rate')), - 'bit_rate_mode': MultiValue(BitRateMode(config, 'BitRateMode', description='audio bit rate mode')), + 'bit_rate_mode': MultiValue(BitRateMode(config, 'BitRate_Mode', description='audio bit rate mode')), 'sampling_rate': MultiValue(Quantity('SamplingRate', units.Hz, description='audio sampling rate')), 'compression': MultiValue(AudioCompression(config, 'Compression_Mode', description='audio compression')), @@ -239,7 +243,7 @@ def __init__(self, config, suggested_path): 'name': Property('Title', description='subtitle track name'), 'language': Language('Language', description='subtitle language'), 'hearing_impaired': None, # populated with HearingImpairedRule - '_closed_caption': Property('captionservicename', private=True), + '_closed_caption': Property('ClosedCaptionsPresent', private=True), 'closed_caption': None, # populated with ClosedCaptionRule 'format': SubtitleFormat(config, 'CodecID', description='subtitle format'), 'forced': YesNo('Forced', hide_value=False, description='subtitle track forced'), @@ -253,6 +257,7 @@ def __init__(self, config, suggested_path): 'audio': { 'language': LanguageRule('audio language'), 'channels': AudioChannelsRule('audio channels'), + 'profile': AudioProfileRule('audio codec profile', override=True), '_atmosrule': AtmosRule('atmos rule'), '_dtshdrule': DtsHdRule('dts-hd rule'), }, @@ -274,8 +279,7 @@ def accepts(self, video_path): def describe(self, video_path, context): """Return video metadata.""" - media_info = self.executor.extract_info(video_path) - data = json.loads(media_info) + data = self.executor.extract_info(video_path) def debug_data(): """Debug data.""" diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index 8299e49..104d086 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -2,6 +2,7 @@ from knowit.rules.audio import AtmosRule from knowit.rules.audio import AudioChannelsRule from knowit.rules.audio import AudioCodecRule +from knowit.rules.audio import AudioProfileRule from knowit.rules.audio import DtsHdRule from knowit.rules.language import LanguageRule from knowit.rules.subtitle import ClosedCaptionRule diff --git a/knowit/rules/audio/__init__.py b/knowit/rules/audio/__init__.py index d0705eb..31cc3cf 100644 --- a/knowit/rules/audio/__init__.py +++ b/knowit/rules/audio/__init__.py @@ -3,3 +3,4 @@ from knowit.rules.audio.channels import AudioChannelsRule from knowit.rules.audio.codec import AudioCodecRule from knowit.rules.audio.dtshd import DtsHdRule +from knowit.rules.audio.profile import AudioProfileRule diff --git a/knowit/rules/audio/profile.py b/knowit/rules/audio/profile.py new file mode 100644 index 0000000..cca48b7 --- /dev/null +++ b/knowit/rules/audio/profile.py @@ -0,0 +1,11 @@ + +from knowit.rule import Rule + + +class AudioProfileRule(Rule): + """Audio Profile rule.""" + + def execute(self, props, pv_props, context): + """Execute the rule against properties.""" + if '_profile' in pv_props and 'profile' not in props: + return pv_props.get('_profile') diff --git a/tests/__init__.py b/tests/__init__.py index a03afa9..f93a70a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -235,7 +235,7 @@ class JsonMedia(DataMedia): @property def input_data(self): """Return the video metadata as json.""" - return read_yaml(self.file_path) + return read_json(self.file_path) def _parse_value(node): diff --git a/tests/conftest.py b/tests/conftest.py index dda3f68..ae2a7af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,7 +41,7 @@ def setup_mediainfo(executor, monkeypatch, options): data = {} extract_info = executor.extract_info monkeypatch.setattr(executor, 'extract_info', - lambda filename: MediaInfo(data[filename]) if filename in data else extract_info(filename)) + lambda filename: data[filename] if filename in data else extract_info(filename)) return data diff --git a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json new file mode 100644 index 0000000..4983f80 --- /dev/null +++ b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json @@ -0,0 +1,207 @@ +{ + "media":{ + "@ref":"/videos/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "track":[ + { + "@type":"General", + "Count":"331", + "StreamCount":"1", + "StreamKind":"General", + "StreamKind_String":"General", + "StreamKindID":"0", + "UniqueID":"233259075675158394332089080443022607862", + "UniqueID_String":"233259075675158394332089080443022607862 (0xAF7C105968F28EDE95280D4670BC05F6)", + "VideoCount":"1", + "AudioCount":"1", + "Video_Format_List":"AVC", + "Video_Format_WithHint_List":"AVC", + "Video_Codec_List":"AVC", + "Audio_Format_List":"DTS XLL", + "Audio_Format_WithHint_List":"DTS XLL", + "Audio_Codec_List":"DTS XLL", + "Audio_Language_List":"English", + "CompleteName":"/videos/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "FolderName":"/videos/Audio Samples", + "FileNameExtension":"7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "FileName":"7.1Ch DTS-HD MA - Speaker Mapping Test File", + "FileExtension":"mkv", + "Format":"Matroska", + "Format_String":"Matroska", + "Format_Url":"https://matroska.org/downloads/windows.html", + "Format_Extensions":"mkv mk3d mka mks", + "Format_Commercial":"Matroska", + "Format_Version":"4", + "FileSize":"40772443", + "FileSize_String":"38.9 MiB", + "FileSize_String1":"39 MiB", + "FileSize_String2":"39 MiB", + "FileSize_String3":"38.9 MiB", + "FileSize_String4":"38.88 MiB", + "Duration":"97.931", + "Duration_String":"1 min 37 s", + "Duration_String1":"1 min 37 s 931 ms", + "Duration_String2":"1 min 37 s", + "Duration_String3":"00:01:37.931", + "Duration_String4":"00:01:37;22", + "Duration_String5":"00:01:37.931 (00:01:37;22)", + "OverallBitRate_Mode":"VBR", + "OverallBitRate_Mode_String":"Variable", + "OverallBitRate":"3330708", + "OverallBitRate_String":"3 331 kb/s", + "FrameRate":"23.976", + "FrameRate_String":"23.976 FPS", + "FrameCount":"2348", + "IsStreamable":"Yes", + "Title":"7.1Ch DTS-HD MA - Speaker Mapping Test File", + "Movie":"7.1Ch DTS-HD MA - Speaker Mapping Test File", + "Encoded_Date":"UTC 2013-12-13 17:49:28", + "File_Modified_Date":"UTC 2016-04-10 07:47:08", + "File_Modified_Date_Local":"2016-04-10 09:47:08", + "Encoded_Application":"mkvmerge v6.6.0 ('The Edge Of The In Between') built on Dec 1 2013 17:55:00", + "Encoded_Application_String":"mkvmerge v6.6.0 ('The Edge Of The In Between') built on Dec 1 2013 17:55:00", + "Encoded_Library":"libebml v1.3.0 + libmatroska v1.4.1", + "Encoded_Library_String":"libebml v1.3.0 + libmatroska v1.4.1" + }, + { + "@type":"Video", + "Count":"379", + "StreamCount":"1", + "StreamKind":"Video", + "StreamKind_String":"Video", + "StreamKindID":"0", + "StreamOrder":"0", + "ID":"1", + "ID_String":"1", + "UniqueID":"11835337130358454411", + "Format":"AVC", + "Format_String":"AVC", + "Format_Info":"Advanced Video Codec", + "Format_Url":"http://developers.videolan.org/x264.html", + "Format_Commercial":"AVC", + "Format_Profile":"Main", + "Format_Level":"4", + "Format_Settings":"CABAC / 4 Ref Frames", + "Format_Settings_CABAC":"Yes", + "Format_Settings_CABAC_String":"Yes", + "Format_Settings_RefFrames":"4", + "Format_Settings_RefFrames_String":"4 frames", + "InternetMediaType":"video/H264", + "CodecID":"V_MPEG4/ISO/AVC", + "CodecID_Url":"http://ffdshow-tryout.sourceforge.net/", + "Duration":"97.931", + "Duration_String":"1 min 37 s", + "Duration_String1":"1 min 37 s 931 ms", + "Duration_String2":"1 min 37 s", + "Duration_String3":"00:01:37.931", + "Duration_String4":"00:01:37;22", + "Duration_String5":"00:01:37.931 (00:01:37;22)", + "BitRate_Mode":"VBR", + "BitRate_Mode_String":"Variable", + "BitRate_Maximum":"40000000", + "BitRate_Maximum_String":"40.0 Mb/s", + "Width":"1920", + "Width_String":"1 920 pixels", + "Height":"1080", + "Height_String":"1 080 pixels", + "Stored_Height":"1088", + "Sampled_Width":"1920", + "Sampled_Height":"1080", + "PixelAspectRatio":"1.000", + "DisplayAspectRatio":"1.778", + "DisplayAspectRatio_String":"16:9", + "FrameRate_Mode":"CFR", + "FrameRate_Mode_String":"Constant", + "FrameRate":"23.976", + "FrameRate_String":"23.976 FPS", + "FrameCount":"2348", + "ColorSpace":"YUV", + "ChromaSubsampling":"4:2:0", + "ChromaSubsampling_String":"4:2:0", + "BitDepth":"8", + "BitDepth_String":"8 bits", + "ScanType":"Progressive", + "ScanType_String":"Progressive", + "Delay":"0.000", + "Delay_String3":"00:00:00.000", + "Delay_Source":"Container", + "Delay_Source_String":"Container", + "Default":"Yes", + "Default_String":"Yes", + "Forced":"No", + "Forced_String":"No", + "BufferSize":"30000000", + "extra":{ + "FrameCount_Source":"General_Duration", + "Duration_Source":"General_Duration" + } + }, + { + "@type":"Audio", + "Count":"282", + "StreamCount":"1", + "StreamKind":"Audio", + "StreamKind_String":"Audio", + "StreamKindID":"0", + "StreamOrder":"1", + "ID":"2", + "ID_String":"2", + "UniqueID":"11679026580382524291", + "Format":"DTS", + "Format_String":"DTS XLL", + "Format_Info":"Digital Theater Systems", + "Format_Url":"https://en.wikipedia.org/wiki/DTS_(sound_system)", + "Format_Commercial":"DTS-HD Master Audio", + "Format_Commercial_IfAny":"DTS-HD Master Audio", + "Format_Settings_Mode":"16", + "Format_Settings_Endianness":"Big", + "Format_AdditionalFeatures":"XLL", + "CodecID":"A_DTS", + "Duration":"97.931", + "Duration_String":"1 min 37 s", + "Duration_String1":"1 min 37 s 931 ms", + "Duration_String2":"1 min 37 s", + "Duration_String3":"00:01:37.931", + "Duration_String5":"00:01:37.931", + "BitRate_Mode":"VBR", + "BitRate_Mode_String":"Variable", + "Channels":"6", + "Channels_String":"6 channels", + "Channels_Original":"8", + "Channels_Original_String":"8 channels", + "ChannelPositions_Original":"Front: L C R, Side: L R, Back: L R, LFE", + "ChannelLayout_Original":"C L R LFE Lsr Rsr Lss Rss", + "SamplesPerFrame":"512", + "SamplingRate":"48000", + "SamplingRate_String":"48.0 kHz", + "SamplingCount":"4700688", + "FrameRate":"93.750", + "FrameRate_String":"93.750 FPS (512 SPF)", + "BitDepth":"24", + "BitDepth_String":"24 bits", + "Compression_Mode":"Lossless", + "Compression_Mode_String":"Lossless", + "Delay":"0.000", + "Delay_String3":"00:00:00.000", + "Delay_Source":"Container", + "Delay_Source_String":"Container", + "Video_Delay":"0.000", + "Video_Delay_String3":"00:00:00.000", + "Title":"7.1Ch DTS-HD MA", + "Language":"en", + "Language_String":"English", + "Language_String1":"English", + "Language_String2":"en", + "Language_String3":"eng", + "Language_String4":"en", + "Default":"Yes", + "Default_String":"Yes", + "Forced":"No", + "Forced_String":"No", + "extra":{ + "SamplingCount_Source":"General_Duration", + "Duration_Source":"General_Duration" + } + } + ] + } +} \ No newline at end of file diff --git a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.xml b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.xml deleted file mode 100644 index 87f0363..0000000 --- a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.xml +++ /dev/null @@ -1,215 +0,0 @@ - - - - - 328 - 1 - General - General - 0 - 233259075675158394332089080443022607862 - 233259075675158394332089080443022607862 (0xAF7C105968F28EDE95280D4670BC05F6) - 1 - 1 - AVC - AVC - AVC - English - DTS - DTS - DTS-HD - English - tests/data/7.1-dts-hd-ma-speaker-mapping-test-file.mkv - tests/data - 7.1-dts-hd-ma-speaker-mapping-test-file - mkv - Matroska - Matroska - https://matroska.org/downloads/windows.html - mkv mk3d mka mks - Matroska - Version 4 / Version 2 - Matroska - Matroska - https://matroska.org/downloads/windows.html - mkv mk3d mka mks - 40772443 - 38.9 MiB - 39 MiB - 39 MiB - 38.9 MiB - 38.88 MiB - 97931 - 1 min 37 s - 1 min 37 s 931 ms - 1 min 37 s - 00:01:37.931 - 00:01:37;22 - 00:01:37.931 (00:01:37;22) - VBR - Variable - 3330708 - 3 331 kb/s - 23.976 - 23.976 FPS - 2348 - Yes - 7.1Ch DTS-HD MA - Speaker Mapping Test File - 7.1Ch DTS-HD MA - Speaker Mapping Test File - UTC 2013-12-13 17:49:28 - UTC 2016-04-10 07:47:08 - 2016-04-10 09:47:08 - mkvmerge v6.6.0 ('The Edge Of The In Between') built on Dec 1 2013 17:55:00 - mkvmerge v6.6.0 ('The Edge Of The In Between') built on Dec 1 2013 17:55:00 - libebml v1.3.0 + libmatroska v1.4.1 - libebml v1.3.0 + libmatroska v1.4.1 - - - 342 - 1 - Video - Video - 0 - 0 - 1 - 1 - 11835337130358454411 - AVC - Advanced Video Codec - http://developers.videolan.org/x264.html - AVC - Main@L4 - CABAC / 4 Ref Frames - Yes - Yes - 4 - 4 frames - video/H264 - V_MPEG4/ISO/AVC - http://ffdshow-tryout.sourceforge.net/ - V_MPEG4/ISO/AVC - AVC - AVC - Advanced Video Codec - http://ffdshow-tryout.sourceforge.net/ - Main@L4 - CABAC / 4 Ref Frames - Yes - 4 - 97931 - 1 min 37 s - 1 min 37 s 931 ms - 1 min 37 s - 00:01:37.931 - 00:01:37;22 - 00:01:37.931 (00:01:37;22) - VBR - Variable - 40000000 - 40.0 Mb/s - 1920 - 1 920 pixels - 1080 - 1 080 pixels - 1088 - 1920 - 1080 - 1.000 - 1.778 - 16:9 - CFR - Constant - 23.976 - 23.976 FPS - 2348 - 8 - 8 bits - 4:2:0 - YUV - 4:2:0 - 4:2:0 - 8 - 8 bits - Progressive - Progressive - PPF - Progressive - 0 - 00:00:00.000 - Container - Container - Yes - Yes - No - No - 30000000 - - - 275 - 1 - Audio - Audio - 0 - 1 - 2 - 2 - 11679026580382524291 - DTS - Digital Theater Systems - DTS - MA / Core - 16 - Big - A_DTS - DTS-HD - DTS-HD - DTS - 97931 - 1 min 37 s - 1 min 37 s 931 ms - 1 min 37 s - 00:01:37.931 - 00:01:37.931 - VBR / CBR - Variable / Constant - Unknown / 1509000 - Unknown / 1 509 kb/s - 8 / 6 - 8 channels / 6 channels - Front: L C R, Side: L R, Back: L R, LFE / Front: L C R, Side: L R, LFE - 3/2/2.1 / 3/2/0.1 - / C L R Ls Rs LFE - 512 - 48000 - 48.0 kHz - 4700688 - 93.750 - 93.750 FPS (512 SPF) - 24 - 24 bits - 24 - 24 bits - Lossless / Lossy - Lossless / Lossy - 0 - 00:00:00.000 - Container - Container - 0 - 00:00:00.000 - 0 - 00:00:00.000 - 7.1Ch DTS-HD MA - en - English - English - en - eng - en - Yes - Yes - No - No - - - diff --git a/tests/test_mediainfo.py b/tests/test_mediainfo.py index 25108c0..b87e95a 100644 --- a/tests/test_mediainfo.py +++ b/tests/test_mediainfo.py @@ -7,7 +7,7 @@ from . import assert_expected, id_func -@pytest.mark.parametrize('media', mediafiles.get_xml_media('mediainfo'), ids=id_func) +@pytest.mark.parametrize('media', mediafiles.get_json_media('mediainfo'), ids=id_func) def test_mediainfo_provider(mediainfo, media, options): # Given mediainfo[media.video_path] = media.input_data From ea97e80bd2c72d6cdbf2be82a57abcb87628567d Mon Sep 17 00:00:00 2001 From: Rato Date: Tue, 23 Mar 2021 20:04:50 +0100 Subject: [PATCH 036/102] Attempt to fix JSONDecodeError --- knowit/providers/mediainfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 4890d49..6baa4e4 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -123,7 +123,7 @@ class MediaInfoCliExecutor(MediaInfoExecutor): } def _execute(self, filename): - return json.loads(check_output([self.location, '--Output=JSON', '--Full', filename]).decode()) + return json.loads(check_output([self.location, '--Output=JSON', '--Full', filename])) @classmethod def create(cls, os_family=None, suggested_path=None): From e377f677dbe720922aa34ecd70f9d5e86272ce01 Mon Sep 17 00:00:00 2001 From: Rato Date: Tue, 23 Mar 2021 20:14:43 +0100 Subject: [PATCH 037/102] Revert last fix. Switch travis to ubuntu focal --- knowit/providers/mediainfo.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 6baa4e4..4890d49 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -123,7 +123,7 @@ class MediaInfoCliExecutor(MediaInfoExecutor): } def _execute(self, filename): - return json.loads(check_output([self.location, '--Output=JSON', '--Full', filename])) + return json.loads(check_output([self.location, '--Output=JSON', '--Full', filename]).decode()) @classmethod def create(cls, os_family=None, suggested_path=None): diff --git a/setup.cfg b/setup.cfg index c4b27e4..522c734 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ install_requires = babelfish >= 0.5.6 ; python_version >= "3.10" enzyme >= 0.4.1 pint >= 0.9 - pymediainfo >= 3.0 + pymediainfo >= 5.0.3 PyYAML >= 3.13 six >= 1.12.0 From ccd62d92c18fdb3a75c2b0ffe69f3177ca8f30c2 Mon Sep 17 00:00:00 2001 From: Rato Date: Tue, 23 Mar 2021 20:18:48 +0100 Subject: [PATCH 038/102] Missing changed file. Switching to ubuntu focal --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cbaa71b..868f0f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false language: python -dist: xenial +dist: focal python: - 3.6 From b7df8fd18dae5171e963e5dc4bb9446d7101852d Mon Sep 17 00:00:00 2001 From: Rato Date: Tue, 23 Mar 2021 20:54:50 +0100 Subject: [PATCH 039/102] Fix major test issue --- tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index f93a70a..96b96ad 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -270,7 +270,7 @@ def check_equals(expected, actual, different, options, prefix=''): check_mapping_equals(expected, actual, different=different, options=options, prefix=prefix) elif is_iterable(expected): check_sequence_equals(expected, actual, different=different, options=options, prefix=prefix) - elif format_property(expected, options) != format_property(actual, options): + elif format_property(options, expected) != format_property(options, actual): different.append((prefix, expected, actual)) From e7a9adaa1f3cf03ea31925671832bf07f3126e51 Mon Sep 17 00:00:00 2001 From: Rato Date: Tue, 23 Mar 2021 21:36:55 +0100 Subject: [PATCH 040/102] Fixes all failing tests --- knowit/properties/duration.py | 4 ++-- knowit/property.py | 5 ++++- knowit/providers/mediainfo.py | 11 ++++++++--- knowit/rules/__init__.py | 2 +- knowit/rules/alternative.py | 15 +++++++++++++++ knowit/rules/audio/__init__.py | 1 - knowit/rules/audio/dtshd.py | 13 ++----------- knowit/rules/audio/profile.py | 11 ----------- ....1-dts-hd-ma-speaker-mapping-test-file.mkv.yml | 2 +- ....1-dts-hd-ma-speaker-mapping-test-file.mkv.yml | 2 +- tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml | 2 +- ...1-dts-hd-ma-speaker-mapping-test-file.mkv.json | 10 +++++----- ....1-dts-hd-ma-speaker-mapping-test-file.mkv.yml | 1 - 13 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 knowit/rules/alternative.py delete mode 100644 knowit/rules/audio/profile.py diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index acbd642..2f3d487 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -19,9 +19,9 @@ def handle(self, value, context): if isinstance(value, timedelta): return value elif isinstance(value, int): - return timedelta(milliseconds=value) + return timedelta(seconds=value) try: - return timedelta(milliseconds=int(float(value))) + return timedelta(seconds=int(float(value))) except ValueError: pass diff --git a/knowit/property.py b/knowit/property.py index d6efc42..dbf9e12 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -153,7 +153,10 @@ def handle( if values is None: return call(values, context) if len(values) > 1 and not self.single: - return [call(item, context) if not _is_unknown(item) else None for item in values] + results = [call(item, context) if not _is_unknown(item) else None for item in values] + results = [r for r in results if r is not None] + if results: + return results return call(values[0], context) @classmethod diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 4890d49..e9e3dbb 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -36,9 +36,9 @@ Provider, ) from ..rules import ( + AlternativeRule, AtmosRule, AudioChannelsRule, - AudioProfileRule, ClosedCaptionRule, DtsHdRule, HearingImpairedRule, @@ -219,13 +219,16 @@ def __init__(self, config, suggested_path): 'duration': Duration('Duration', description='audio duration'), 'size': Quantity('StreamSize', units.byte, description='audio stream size'), 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), + '_codec': MultiValue(AudioCodec(config, 'CodecID_Hint', description='audio codec'), private=True), 'profile': MultiValue(AudioProfile(config, 'Format_Profile', description='audio codec profile'), delimiter=' / '), '_profile': MultiValue(AudioProfile(config, 'Format_AdditionalFeatures', description='audio codec additional features'), delimiter=' / ', private=True), - 'channels_count': MultiValue(AudioChannels('Channels', description='audio channels count')), + 'channels_count': MultiValue(AudioChannels('Channels_Original', description='audio channels count')), + '_channels_count': MultiValue(AudioChannels('Channels', description='audio channels count'), + private=True), 'channel_positions': MultiValue(name='ChannelPositions_String2', handler=(lambda x, *args: x), delimiter=' / ', private=True, description='audio channels position'), 'channels': None, # populated with AudioChannelsRule @@ -256,8 +259,10 @@ def __init__(self, config, suggested_path): }, 'audio': { 'language': LanguageRule('audio language'), + 'channels_count': AlternativeRule('audio channels count', 'channels_count', override=True), 'channels': AudioChannelsRule('audio channels'), - 'profile': AudioProfileRule('audio codec profile', override=True), + 'codec': AlternativeRule('audio codec', 'codec', override=True), + 'profile': AlternativeRule('audio codec profile', 'profile', override=True), '_atmosrule': AtmosRule('atmos rule'), '_dtshdrule': DtsHdRule('dts-hd rule'), }, diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index 104d086..bdd89fa 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -1,8 +1,8 @@ +from knowit.rules.alternative import AlternativeRule from knowit.rules.audio import AtmosRule from knowit.rules.audio import AudioChannelsRule from knowit.rules.audio import AudioCodecRule -from knowit.rules.audio import AudioProfileRule from knowit.rules.audio import DtsHdRule from knowit.rules.language import LanguageRule from knowit.rules.subtitle import ClosedCaptionRule diff --git a/knowit/rules/alternative.py b/knowit/rules/alternative.py new file mode 100644 index 0000000..796e1fd --- /dev/null +++ b/knowit/rules/alternative.py @@ -0,0 +1,15 @@ + +from knowit.rule import Rule + + +class AlternativeRule(Rule): + """Alternative rule.""" + + def __init__(self, name, prop_name: str, **kwargs): + super().__init__(name, **kwargs) + self.prop_name = prop_name + + def execute(self, props, pv_props, context): + """Execute the rule against properties.""" + if f'_{self.prop_name}' in pv_props and self.prop_name not in props: + return pv_props.get(f'_{self.prop_name}') diff --git a/knowit/rules/audio/__init__.py b/knowit/rules/audio/__init__.py index 31cc3cf..d0705eb 100644 --- a/knowit/rules/audio/__init__.py +++ b/knowit/rules/audio/__init__.py @@ -3,4 +3,3 @@ from knowit.rules.audio.channels import AudioChannelsRule from knowit.rules.audio.codec import AudioCodecRule from knowit.rules.audio.dtshd import DtsHdRule -from knowit.rules.audio.profile import AudioProfileRule diff --git a/knowit/rules/audio/dtshd.py b/knowit/rules/audio/dtshd.py index d45e9e9..53f47c8 100644 --- a/knowit/rules/audio/dtshd.py +++ b/knowit/rules/audio/dtshd.py @@ -17,14 +17,5 @@ def _redefine(cls, props, name, index): def execute(self, props, pv_props, context): """Execute the rule against properties.""" - if props.get('codec') == 'DTS-HD': - index = None - for i, profile in enumerate(props.get('profile', [])): - if profile and profile.upper() != 'CORE': - index = i - break - - if index is not None: - for name in ('profile', 'channels_count', 'bit_rate', - 'bit_rate_mode', 'sampling_rate', 'compression'): - self._redefine(props, name, index) + if props.get('codec') == 'DTS' and props.get('profile') in ('Master Audio', 'High Resolution Audio'): + props['codec'] = 'DTS-HD' diff --git a/knowit/rules/audio/profile.py b/knowit/rules/audio/profile.py deleted file mode 100644 index cca48b7..0000000 --- a/knowit/rules/audio/profile.py +++ /dev/null @@ -1,11 +0,0 @@ - -from knowit.rule import Rule - - -class AudioProfileRule(Rule): - """Audio Profile rule.""" - - def execute(self, props, pv_props, context): - """Execute the rule against properties.""" - if '_profile' in pv_props and 'profile' not in props: - return pv_props.get('_profile') diff --git a/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml b/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml index e36cbf4..19a7f0e 100644 --- a/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml +++ b/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml @@ -1,5 +1,5 @@ title: 7.1Ch DTS-HD MA - Speaker Mapping Test File -path: tests/data/7.1-dts-hd-ma-speaker-mapping-test-file.mkv +path: tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv duration: 0:01:37 container: mkv video: diff --git a/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml b/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml index e9a85d5..a8b28dc 100644 --- a/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml +++ b/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml @@ -1,5 +1,5 @@ title: 7.1Ch DTS-HD MA - Speaker Mapping Test File -path: tests/data/7.1-dts-hd-ma-speaker-mapping-test-file.mkv +path: tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv duration: 0:01:37 size: 40.77 MB bit_rate: 3.3 Mbps diff --git a/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml b/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml index ba8cbde..3ce683e 100644 --- a/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml +++ b/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml @@ -1,5 +1,5 @@ title: Big Buck Bunny - test 8 -path: tests/data/videos/test5.mkv +path: test5.mkv duration: 0:00:46 size: 31.76 MB bit_rate: 5.4 Mbps diff --git a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json index 4983f80..c184250 100644 --- a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json +++ b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.json @@ -1,6 +1,6 @@ { "media":{ - "@ref":"/videos/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "@ref":"tests/data/7.1-dts-hd-ma-speaker-mapping-test-file.mkv", "track":[ { "@type":"General", @@ -20,10 +20,10 @@ "Audio_Format_WithHint_List":"DTS XLL", "Audio_Codec_List":"DTS XLL", "Audio_Language_List":"English", - "CompleteName":"/videos/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", - "FolderName":"/videos/Audio Samples", - "FileNameExtension":"7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", - "FileName":"7.1Ch DTS-HD MA - Speaker Mapping Test File", + "CompleteName":"tests/data/7.1-dts-hd-ma-speaker-mapping-test-file.mkv", + "FolderName":"tests/data", + "FileNameExtension":"7.1-dts-hd-ma-speaker-mapping-test-file.mkv", + "FileName":"7.1-dts-hd-ma-speaker-mapping-test-file", "FileExtension":"mkv", "Format":"Matroska", "Format_String":"Matroska", diff --git a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml index abb836c..b265188 100644 --- a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml +++ b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml @@ -1,6 +1,5 @@ title: 7.1Ch DTS-HD MA - Speaker Mapping Test File path: tests/data/7.1-dts-hd-ma-speaker-mapping-test-file.mkv - Mapping Test File.mkv duration: 0:01:37 size: 40.77 MB bit_rate: 3.3 Mbps From 334ab96f7c4c0c8e1b300ff6d9b5169e8ccc4bd6 Mon Sep 17 00:00:00 2001 From: Rato Date: Tue, 23 Mar 2021 22:12:34 +0100 Subject: [PATCH 041/102] Ignore media_type in tests since it differs from environment to environment --- tests/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index 96b96ad..9e53519 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -291,6 +291,9 @@ def check_mapping_equals(expected, actual, different, options, prefix=''): return for expected_key, expected_value in expected.items(): + if expected_key == 'media_type': + continue + if expected_key not in actual: different.append((prefix + expected_key, expected_value, None)) continue From 80413f6b77c39e7986e1651cb834b6f50f2bdd97 Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 01:15:49 -0400 Subject: [PATCH 042/102] Fix imports --- tests/conftest.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ae2a7af..908784f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ +from unittest.mock import Mock -from pymediainfo import MediaInfo import pytest from knowit import api @@ -8,11 +8,6 @@ from knowit.providers.ffmpeg import FFmpegCliExecutor, FFmpegExecutor from knowit.providers.mediainfo import MediaInfoCTypesExecutor, MediaInfoCliExecutor, MediaInfoExecutor -try: - from mock import Mock -except ImportError: - from unittest.mock import Mock - @pytest.fixture def context(): From 80427b9f91c41a3ca276ab12cf4e88b5288dad2c Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 01:27:20 -0400 Subject: [PATCH 043/102] Fix media file paths fail on Windows Normalize paths by round-tripping through pathlib.Path --- tests/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index 9e53519..adf6a20 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,6 @@ - import json import os +import pathlib import re import sys from datetime import timedelta @@ -42,6 +42,10 @@ }) +def normalize_path(path: str): + return os.fspath(pathlib.Path(path)) + + def parameters_from_yaml(name, input_key=None, expected_key=None): package_name, resource_name = name.split('.', 1) @@ -300,6 +304,11 @@ def check_mapping_equals(expected, actual, different, options, prefix=''): actual_value = actual[expected_key] key = prefix + expected_key + + if expected_key == 'path': + expected_value = normalize_path(expected_value) + actual_value = normalize_path(actual_value) + check_equals(expected_value, actual_value, different=different, options=options, prefix=key) for actual_key, actual_value in actual.items(): From a1425be7c789244f1f2de6d3baff5a49c22a7896 Mon Sep 17 00:00:00 2001 From: Labrys Date: Tue, 23 Mar 2021 19:34:16 -0400 Subject: [PATCH 044/102] Fix test name --- tests/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 7f5b47c..7e85979 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -15,7 +15,7 @@ (None, None, 'unix'), ] ) -def test_detect_os_is_windows(os_name, sys_platform, expected): +def test_detect_os(os_name, sys_platform, expected): with patch('knowit.utils.os') as mock_os: mock_os.name = os_name with patch('knowit.utils.sys') as mock_sys: From cd5c4c4c9d70c39de2763f2cc7c16fda81ccc1a6 Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 00:02:38 -0400 Subject: [PATCH 045/102] Refactor names[os_family] -> family_names --- knowit/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index ebd2c3c..bb5b4d0 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -79,12 +79,13 @@ def define_candidate( ) -> typing.Generator[str, None, None]: """Generate candidate list for the given parameters.""" os_family = os_family or detect_os() + family_names = names[os_family] for location in (suggested_path, ) + locations[os_family]: if not location: continue if location == '__PATH__': - for name in names[os_family]: + for name in family_names: if os_family == 'windows': for path in os.environ['PATH'].split(';'): yield os.path.join(path, name) @@ -93,7 +94,7 @@ def define_candidate( elif os.path.isfile(location): yield location elif os.path.isdir(location): - for name in names[os_family]: + for name in family_names: cmd = os.path.join(location, name) if os.path.isfile(cmd): yield cmd From 8f575d33c8f25406560bcbd5fa9b55c0a97853eb Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 00:04:03 -0400 Subject: [PATCH 046/102] Refactor suggested_path + locations[os_family] -> all_locations --- knowit/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/knowit/utils.py b/knowit/utils.py index bb5b4d0..2556f1d 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -80,7 +80,8 @@ def define_candidate( """Generate candidate list for the given parameters.""" os_family = os_family or detect_os() family_names = names[os_family] - for location in (suggested_path, ) + locations[os_family]: + all_locations = (suggested_path, ) + locations[os_family] + for location in all_locations: if not location: continue From 508b2571012cd9166b0cf5a91a8589e0c82d4025 Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 00:18:47 -0400 Subject: [PATCH 047/102] Refactor define_candidates Separate the logic for building candidate names from selecting os family candidates --- knowit/utils.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index 2556f1d..c2ab271 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -77,16 +77,26 @@ def define_candidate( os_family: typing.Optional[OS_FAMILY] = None, suggested_path: typing.Optional[str] = None, ) -> typing.Generator[str, None, None]: - """Generate candidate list for the given parameters.""" + """Select family-specific options and generate possible candidates.""" os_family = os_family or detect_os() family_names = names[os_family] all_locations = (suggested_path, ) + locations[os_family] - for location in all_locations: + yield from build_candidates(all_locations, family_names) + + +def build_candidates( + locations: typing.Iterable[str], + names: typing.Iterable[str], + os_family: typing.Optional[OS_FAMILY] = None, +): + """Build candidate names """ + os_family = os_family or detect_os() + for location in locations: if not location: continue if location == '__PATH__': - for name in family_names: + for name in names: if os_family == 'windows': for path in os.environ['PATH'].split(';'): yield os.path.join(path, name) @@ -95,7 +105,7 @@ def define_candidate( elif os.path.isfile(location): yield location elif os.path.isdir(location): - for name in family_names: + for name in names: cmd = os.path.join(location, name) if os.path.isfile(cmd): yield cmd From cb265b5880ecc8f7c1d0f5b1d13b0f8ea8e94a8d Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 00:28:30 -0400 Subject: [PATCH 048/102] Refactor build_candidate Separate the logic for building candidate names from building candidates on the system PATh --- knowit/utils.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/knowit/utils.py b/knowit/utils.py index c2ab271..0a4d8c3 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -87,21 +87,13 @@ def define_candidate( def build_candidates( locations: typing.Iterable[str], names: typing.Iterable[str], - os_family: typing.Optional[OS_FAMILY] = None, ): - """Build candidate names """ - os_family = os_family or detect_os() + """Build candidate names.""" for location in locations: if not location: continue - if location == '__PATH__': - for name in names: - if os_family == 'windows': - for path in os.environ['PATH'].split(';'): - yield os.path.join(path, name) - else: - yield name + yield from build_path_candidates(names) elif os.path.isfile(location): yield location elif os.path.isdir(location): @@ -109,3 +101,20 @@ def build_candidates( cmd = os.path.join(location, name) if os.path.isfile(cmd): yield cmd + + +def build_path_candidates( + names: typing.Iterable[str], + os_family: typing.Optional[OS_FAMILY] = None, +): + """Build candidate names on environment PATH.""" + os_family = os_family or detect_os() + if os_family != 'windows': + yield from names + else: + paths = os.environ['PATH'].split(';') + yield from ( + os.path.join(path, name) + for path in paths + for name in names + ) From 4281cb6983c4a5abd004d54600a1a72645dd25e5 Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 01:12:07 -0400 Subject: [PATCH 049/102] Add test_build_path_candidates_for_specified_os --- tests/test_utils.py | 48 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 7e85979..33d4dd7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,8 +1,9 @@ from unittest.mock import patch import pytest +import os -from knowit.utils import detect_os +from knowit.utils import detect_os, build_path_candidates @pytest.mark.parametrize( @@ -21,3 +22,48 @@ def test_detect_os(os_name, sys_platform, expected): with patch('knowit.utils.sys') as mock_sys: mock_sys.platform = sys_platform assert detect_os() == expected + + +@pytest.mark.parametrize( + 'os_family, path, names, expected', [ + ( + 'windows', + r'C:\Application;C:\Program Files\Application', + ('some.dll', 'some.exe', 'another.exe'), + [ + r'C:\Application\some.dll', + r'C:\Application\some.exe', + r'C:\Application\another.exe', + r'C:\Program Files\Application\some.dll', + r'C:\Program Files\Application\some.exe', + r'C:\Program Files\Application\another.exe', + ], + ), + ( + 'macos', + '/usr/sbin:/usr/bin:/sbin:/bin', + ('some.dll', 'binary', 'another_binary'), + [ + 'some.dll', + 'binary', + 'another_binary', + ], + ), + ( + 'linux', + '/usr/sbin:/usr/bin:/sbin:/bin', + ('some.dll', 'binary', 'another_binary'), + [ + 'some.dll', + 'binary', + 'another_binary', + ], + ), + ], +) +def test_build_path_candidates_for_specified_os(names, os_family, path, expected): + with patch('knowit.utils.os') as mock_os: + mock_os.environ = {'PATH': path} + mock_os.path = os.path # don't mock os.path functions + candidates = build_path_candidates(names, os_family) + assert list(candidates) == expected From b79272eea34f8e560bd42522a7441444639d9f8e Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 01:12:36 -0400 Subject: [PATCH 050/102] Fix tox to test with very verbose (-vv) option --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6bacdaf..79a99ce 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ deps = requests >= 2.21.0 commands = pip install . - pytest tests --cov-report term --cov-report html --cov knowit --verbose {posargs} + pytest tests --cov-report term --cov-report html --cov knowit -vv {posargs} [testenv:lint] deps = From 2df29020ee8149ede56ca043d589c09990c2957d Mon Sep 17 00:00:00 2001 From: Rato Date: Wed, 24 Mar 2021 08:22:22 +0100 Subject: [PATCH 051/102] Add debug data to failing tests --- tests/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index adf6a20..527ae98 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -326,4 +326,7 @@ def assert_expected(expected, actual, options=None): for (key, expected, actual) in different: print('{0}: Expected {1} got {2}'.format(key, expected, actual), file=sys.stderr) + if different and options and options['debug_data']: + print(options['debug_data']()) + assert not different From 021c63501078b101cb260972f15017714bba7709 Mon Sep 17 00:00:00 2001 From: Rato Date: Wed, 24 Mar 2021 21:24:08 +0100 Subject: [PATCH 052/102] Fixes MP3 detection in different versions of MediaInfo --- knowit/property.py | 7 ++++++- knowit/providers/mediainfo.py | 2 -- tests/__init__.py | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/knowit/property.py b/knowit/property.py index dbf9e12..46eeb0a 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -142,6 +142,11 @@ def handle( context: typing.Mapping, ) -> typing.Union[T, typing.List[T]]: """Handle properties with multiple values.""" + call = self.handler or self.prop.handle + result = call(value, context) + if result is not None: + return result + if isinstance(value, list): if len(value) == 1: values = self._split(value[0], self.delimiter) @@ -149,7 +154,7 @@ def handle( values = value else: values = self._split(value, self.delimiter) - call = self.handler or self.prop.handle + if values is None: return call(values, context) if len(values) > 1 and not self.single: diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index e9e3dbb..7895d88 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -219,7 +219,6 @@ def __init__(self, config, suggested_path): 'duration': Duration('Duration', description='audio duration'), 'size': Quantity('StreamSize', units.byte, description='audio stream size'), 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), - '_codec': MultiValue(AudioCodec(config, 'CodecID_Hint', description='audio codec'), private=True), 'profile': MultiValue(AudioProfile(config, 'Format_Profile', description='audio codec profile'), delimiter=' / '), @@ -261,7 +260,6 @@ def __init__(self, config, suggested_path): 'language': LanguageRule('audio language'), 'channels_count': AlternativeRule('audio channels count', 'channels_count', override=True), 'channels': AudioChannelsRule('audio channels'), - 'codec': AlternativeRule('audio codec', 'codec', override=True), 'profile': AlternativeRule('audio codec profile', 'profile', override=True), '_atmosrule': AtmosRule('atmos rule'), '_dtshdrule': DtsHdRule('dts-hd rule'), diff --git a/tests/__init__.py b/tests/__init__.py index 527ae98..563ea9a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -318,7 +318,9 @@ def check_mapping_equals(expected, actual, different, options, prefix=''): def assert_expected(expected, actual, options=None): + version = None if 'provider' in actual: + version = actual['provider']['version'] del actual['provider']['version'] different = [] @@ -327,6 +329,7 @@ def assert_expected(expected, actual, options=None): print('{0}: Expected {1} got {2}'.format(key, expected, actual), file=sys.stderr) if different and options and options['debug_data']: + print(f'Version: {version}') print(options['debug_data']()) assert not different From 8f4047285449c74b40594b959d4163e72640232c Mon Sep 17 00:00:00 2001 From: Rato Date: Wed, 24 Mar 2021 22:41:37 +0100 Subject: [PATCH 053/102] Initial implementation for mkvmerge provider --- knowit/__main__.py | 8 +- knowit/api.py | 2 + knowit/properties/duration.py | 8 +- knowit/providers/__init__.py | 1 + knowit/providers/mediainfo.py | 6 +- knowit/providers/mkvmerge.py | 247 ++++++++++++++++++++++++++++++++++ knowit/rules/language.py | 3 + tests/__init__.py | 2 + 8 files changed, 271 insertions(+), 6 deletions(-) create mode 100644 knowit/providers/mkvmerge.py diff --git a/knowit/__main__.py b/knowit/__main__.py index d08532d..6c4d53d 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -43,7 +43,7 @@ def build_argument_parser() -> ArgumentParser: '-p', '--provider', dest='provider', - help='The provider to be used: mediainfo, ffmpeg or enzyme.', + help='The provider to be used: mediainfo, ffmpeg, mkvmerge or enzyme.', type=str, ) @@ -95,6 +95,12 @@ def build_argument_parser() -> ArgumentParser: help='The location to search for FFmpeg (ffprobe) binaries', type=str, ) + conf_opts.add_argument( + '--mkvmerge', + dest='mkvmerge', + help='The location to search for mkvmerge binaries', + type=str, + ) information_opts = opts.add_argument_group('Information') information_opts.add_argument( diff --git a/knowit/api.py b/knowit/api.py index 886e043..4df7806 100644 --- a/knowit/api.py +++ b/knowit/api.py @@ -9,11 +9,13 @@ EnzymeProvider, FFmpegProvider, MediaInfoProvider, + MkvMergeProvider, ) _provider_map = { 'mediainfo': MediaInfoProvider, 'ffmpeg': FFmpegProvider, + 'mkvmerge': MkvMergeProvider, 'enzyme': EnzymeProvider, } diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 2f3d487..18fa3b4 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -14,14 +14,18 @@ class Duration(Property): r'(?P\d{3})' r'(?P\d{3})?\d*)?') + def __init__(self, name: str, resolution=1, *args, **kwargs): + super().__init__(name, *args, **kwargs) + self.resolution = resolution + def handle(self, value, context): """Return duration as timedelta.""" if isinstance(value, timedelta): return value elif isinstance(value, int): - return timedelta(seconds=value) + return timedelta(microseconds=value * self.resolution) try: - return timedelta(seconds=int(float(value))) + return timedelta(microseconds=int(float(value) * self.resolution)) except ValueError: pass diff --git a/knowit/providers/__init__.py b/knowit/providers/__init__.py index 4224348..34ea048 100644 --- a/knowit/providers/__init__.py +++ b/knowit/providers/__init__.py @@ -3,3 +3,4 @@ from knowit.providers.enzyme import EnzymeProvider from knowit.providers.ffmpeg import FFmpegProvider from knowit.providers.mediainfo import MediaInfoProvider +from knowit.providers.mkvmerge import MkvMergeProvider diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 7895d88..b8b1a1f 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -183,7 +183,7 @@ def __init__(self, config, suggested_path): 'general': { 'title': Property('Title', description='media title'), 'path': Property('CompleteName', description='media path'), - 'duration': Duration('Duration', description='media duration'), + 'duration': Duration('Duration', resolution=1000000, description='media duration'), 'size': Quantity('FileSize', units.byte, description='media size'), 'bit_rate': Quantity('OverallBitRate', units.bps, description='media bit rate'), }, @@ -191,7 +191,7 @@ def __init__(self, config, suggested_path): 'id': Basic('ID', int, allow_fallback=True, description='video track number'), 'name': Property('Title', description='video track name'), 'language': Language('Language', description='video language'), - 'duration': Duration('Duration', description='video duration'), + 'duration': Duration('Duration', resolution=1000000, description='video duration'), 'size': Quantity('StreamSize', units.byte, description='video stream size'), 'width': Quantity('Width', units.pixel), 'height': Quantity('Height', units.pixel), @@ -216,7 +216,7 @@ def __init__(self, config, suggested_path): 'id': Basic('ID', int, allow_fallback=True, description='audio track number'), 'name': Property('Title', description='audio track name'), 'language': Language('Language', description='audio language'), - 'duration': Duration('Duration', description='audio duration'), + 'duration': Duration('Duration', resolution=1000000, description='audio duration'), 'size': Quantity('StreamSize', units.byte, description='audio stream size'), 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), 'profile': MultiValue(AudioProfile(config, 'Format_Profile', diff --git a/knowit/providers/mkvmerge.py b/knowit/providers/mkvmerge.py new file mode 100644 index 0000000..ba67d14 --- /dev/null +++ b/knowit/providers/mkvmerge.py @@ -0,0 +1,247 @@ + +import json +import logging +import re +from logging import NullHandler, getLogger +from subprocess import check_output + +from knowit.properties import ( + AudioCodec, + Basic, + Duration, + Language, + Quantity, + VideoCodec, + YesNo, +) +from knowit.property import Property +from knowit.provider import ( + MalformedFileError, + Provider, +) +from knowit.rules import ( + AudioChannelsRule, + ClosedCaptionRule, + HearingImpairedRule, + LanguageRule, + ResolutionRule, +) +from knowit.serializer import get_json_encoder +from knowit.units import units +from knowit.utils import detect_os, define_candidate + +logger = getLogger(__name__) +logger.addHandler(NullHandler()) + +WARN_MSG = r''' +========================================================================================= +mkvmerge not found on your system or could not be loaded. +Visit https://mkvtoolnix.download to download it. +If you still have problems, please check if the downloaded version matches your system. +To load mkvmerge from a specific location, please define the location as follow: + knowit --mkvmerge /usr/local/mkvmerge/bin + knowit --mkvmerge /usr/local/mkvmerge/bin/ffprobe + knowit --mkvmerge "C:\Program Files\mkvmerge" + knowit --mkvmerge C:\Software\mkvmerge.exe +========================================================================================= +''' + + +class MkvMergeExecutor: + """Executor that knows how to execute mkvmerge.""" + + version_re = re.compile(r'\bv(?P[^\b\s]+)') + locations = { + 'unix': ('/usr/local/mkvmerge/lib', '/usr/local/mkvmerge/bin', '__PATH__'), + 'windows': ('__PATH__', ), + 'macos': ('__PATH__', ), + } + + def __init__(self, location, version): + """Initialize the object.""" + self.location = location + self.version = version + + def extract_info(self, filename): + """Extract media info.""" + json_dump = self._execute(filename) + return json.loads(json_dump) + + def _execute(self, filename): + raise NotImplementedError + + @classmethod + def _get_version(cls, output): + match = cls.version_re.search(output) + if match: + version = match.groupdict()['version'] + return version + + @classmethod + def get_executor_instance(cls, suggested_path=None): + """Return executor instance.""" + os_family = detect_os() + logger.debug('Detected os: %s', os_family) + for exec_cls in (MkvMergeCliExecutor, ): + executor = exec_cls.create(os_family, suggested_path) + if executor: + return executor + + +class MkvMergeCliExecutor(MkvMergeExecutor): + """Executor that uses mkvmerge cli.""" + + names = { + 'unix': ('mkvmerge', ), + 'windows': ('mkvmerge.exe', ), + 'macos': ('mkvmerge', ), + } + + def _execute(self, filename): + return check_output([self.location, '-i', '-F', 'json', filename]).decode() + + @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): + try: + output = check_output([candidate, '--version']).decode() + version = cls._get_version(output) + if version: + logger.debug('MkvMerge cli detected: %s v%s', candidate, version) + return MkvMergeCliExecutor(candidate, version.split('.')) + except OSError: + pass + + +class MkvMergeProvider(Provider): + """MkvMerge Provider.""" + + def __init__(self, config, suggested_path=None, *args, **kwargs): + """Init method.""" + super().__init__(config, { + 'general': { + 'title': Property('title', description='media title'), + 'duration': Duration('duration', resolution=0.001, description='media duration'), + }, + 'video': { + 'id': Basic('number', int, description='video track number'), + 'name': Property('name', description='video track name'), + 'language': Language('language', description='video language'), + 'language_ietf': Language('language_ietf', description='video language', private=True), + 'width': Quantity('width', units.pixel), # TODO: extract resolution + 'height': Quantity('height', units.pixel), + 'scan_type': YesNo('interlaced', yes='Interlaced', no='Progressive', default='Progressive', + description='video scan type'), + 'resolution': None, # populated with ResolutionRule + # 'bit_depth', Property('bit_depth', Integer('video bit depth')), + 'codec': VideoCodec(config, 'codec_id', description='video codec'), + 'forced': YesNo('forced_track', hide_value=False, description='video track forced'), + 'default': YesNo('default_track', hide_value=False, description='video track default'), + 'enabled': YesNo('enabled_track', hide_value=True, description='video track enabled'), + }, + 'audio': { + 'id': Basic('number', int, description='audio track number'), + 'name': Property('name', description='audio track name'), + 'language': Language('language', description='audio language'), + 'language_ietf': Language('language_ietf', description='audio language', private=True), + 'codec': AudioCodec(config, 'codec_id', description='audio codec'), + 'channels_count': Basic('audio_channels', int, description='audio channels count'), + 'channels': None, # populated with AudioChannelsRule + 'forced': YesNo('forced_track', hide_value=False, description='audio track forced'), + 'default': YesNo('default_track', hide_value=False, description='audio track default'), + 'enabled': YesNo('enabled_track', hide_value=True, description='audio track enabled'), + }, + 'subtitle': { + 'id': Basic('number', int, description='subtitle track number'), + 'name': Property('name', description='subtitle track name'), + 'language': Language('language', description='subtitle language'), + 'language_ietf': Language('language_ietf', description='subtitle language', private=True), + 'hearing_impaired': None, # populated with HearingImpairedRule + 'closed_caption': None, # populated with ClosedCaptionRule + 'forced': YesNo('forced_track', hide_value=False, description='subtitle track forced'), + 'default': YesNo('default_track', hide_value=False, description='subtitle track default'), + 'enabled': YesNo('enabled_track', hide_value=True, description='subtitle track enabled'), + }, + }, { + 'video': { + 'language': LanguageRule('video language', override=True), + 'resolution': ResolutionRule('video resolution'), + }, + 'audio': { + 'language': LanguageRule('audio language', override=True), + 'channels': AudioChannelsRule('audio channels'), + }, + 'subtitle': { + 'language': LanguageRule('subtitle language', override=True), + 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired'), + 'closed_caption': ClosedCaptionRule('closed caption'), + } + }) + self.executor = MkvMergeExecutor.get_executor_instance(suggested_path) + + def accepts(self, video_path): + """Accept Matroska videos when mkvmerge is available.""" + if self.executor is None: + logger.warning(WARN_MSG) + self.executor = False + + return self.executor and video_path.lower().endswith(('.mkv', '.mka', '.mks')) + + @classmethod + def extract_info(cls, video_path): + """Extract info from the video.""" + return json.loads(check_output(['mkvmerge', '-i', '-F', video_path]).decode()) + + def describe(self, video_path, context): + """Return video metadata.""" + data = self.executor.extract_info(video_path) + + def debug_data(): + """Debug data.""" + return json.dumps(data, cls=get_json_encoder(context), indent=4, ensure_ascii=False) + + context['debug_data'] = debug_data + + if logger.isEnabledFor(logging.DEBUG): + logger.debug('Video %r scanned using mkvmerge %r has raw data:\n%s', + video_path, self.executor.location, debug_data()) + + def merge_properties(target: dict): + """Merge properties sub properties into the target container.""" + return {**{k: v for k, v in target.items() if k != 'properties'}, **target.get('properties', {})} + + general_track = merge_properties(data.get('container', {})) + video_tracks = [] + audio_tracks = [] + subtitle_tracks = [] + for track in data.get('tracks'): + track_type = track.get('type') + merged = merge_properties(track) + if track_type == 'video': + video_tracks.append(merged) + elif track_type == 'audio': + audio_tracks.append(merged) + elif track_type == 'subtitles': + subtitle_tracks.append(merged) + + result = self._describe_tracks(video_path, general_track, video_tracks, audio_tracks, subtitle_tracks, context) + + if not result: + raise MalformedFileError + + result['provider'] = { + 'name': 'mkvmerge', + 'version': self.version + } + + return result + + @property + def version(self): + """Return mkvmerge version information.""" + if not self.executor: + return {} + version = '.'.join(map(str, self.executor.version)) + + return {self.executor.location: f'v{version}'} diff --git a/knowit/rules/language.py b/knowit/rules/language.py index 9a7355a..2bbe622 100644 --- a/knowit/rules/language.py +++ b/knowit/rules/language.py @@ -17,6 +17,9 @@ class LanguageRule(Rule): def execute(self, props, pv_props, context): """Language detection using name.""" + if 'language_ietf' in pv_props: + return pv_props['language_ietf'] + if 'language' in props: return diff --git a/tests/__init__.py b/tests/__init__.py index 563ea9a..90e3c9b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -127,6 +127,8 @@ def _provider_datafiles(): for provider in provider_names: files = [] data_path = os.path.join('tests', 'data', provider) + if not os.path.isdir(data_path): + continue for path in os.listdir(data_path): if not path.lower().endswith(YAML_EXTENSIONS): files.append(os.path.join(data_path, path)) From 6def30f2dbba7da354e7c92d096ab71af93ad155 Mon Sep 17 00:00:00 2001 From: Rato Date: Thu, 25 Mar 2021 07:25:40 +0100 Subject: [PATCH 054/102] Fixes path comparison in test_build_path_candidates_for_specified_os --- tests/test_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 33d4dd7..057fd11 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,4 @@ +import typing from unittest.mock import patch import pytest @@ -66,4 +67,9 @@ def test_build_path_candidates_for_specified_os(names, os_family, path, expected mock_os.environ = {'PATH': path} mock_os.path = os.path # don't mock os.path functions candidates = build_path_candidates(names, os_family) - assert list(candidates) == expected + + def normalize_paths(paths: typing.Iterable[str]): + """replace all slashes to a forward slash for comparison purposes.""" + return [p.replace('\\', '/') for p in paths] + + assert normalize_paths(candidates) == normalize_paths(expected) From 509d4fd1b25a61e2b52b4482c7fbee88a5ecabf9 Mon Sep 17 00:00:00 2001 From: Labrys Date: Wed, 24 Mar 2021 18:04:42 -0400 Subject: [PATCH 055/102] Add type-hints --- knowit/serializer.py | 25 ++++++++++++++++++++----- knowit/utils.py | 4 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/knowit/serializer.py b/knowit/serializer.py index 5b4efac..253172d 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -1,5 +1,6 @@ - +import datetime import json +import typing from datetime import timedelta import babelfish @@ -81,7 +82,10 @@ class CustomLoader(yaml.Loader): return CustomLoader -def format_duration(duration, profile='default'): +def format_duration( + duration: datetime.timedelta, + profile='default', +) -> typing.Union[str, float]: if profile == 'technical': return str(duration) @@ -103,14 +107,20 @@ def format_duration(duration, profile='default'): return f'{hours}:{minutes:02d}:{seconds:02d}' -def format_language(language, profile='default'): +def format_language( + language: babelfish.language.Language, + profile: str = 'default', +) -> str: if profile in ('default', 'human'): return str(language.name) return str(language) -def format_quantity(quantity, profile='default'): +def format_quantity( + quantity, + profile='default', +) -> str: """Human friendly format.""" if profile == 'code': return quantity.magnitude @@ -130,7 +140,12 @@ def format_quantity(quantity, profile='default'): return str(quantity) -def _format_quantity(num, unit='B', binary=False, precision=2): +def _format_quantity( + num, + unit: str = 'B', + binary: bool = False, + precision: int = 2, +) -> str: fmt_pattern = '{value:3.%sf} {prefix}{affix}{unit}' % precision factor = 1024. if binary else 1000. binary_affix = 'i' if binary else '' diff --git a/knowit/utils.py b/knowit/utils.py index 0a4d8c3..06e779f 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -87,7 +87,7 @@ def define_candidate( def build_candidates( locations: typing.Iterable[str], names: typing.Iterable[str], -): +) -> typing.Generator[str, None, None]: """Build candidate names.""" for location in locations: if not location: @@ -106,7 +106,7 @@ def build_candidates( def build_path_candidates( names: typing.Iterable[str], os_family: typing.Optional[OS_FAMILY] = None, -): +) -> typing.Generator[str, None, None]: """Build candidate names on environment PATH.""" os_family = os_family or detect_os() if os_family != 'windows': From 75c0902292d35edda0d248947616d7e1b8d512f9 Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 01:40:50 -0400 Subject: [PATCH 056/102] Refactor format_property(context: dict, *) -> format_property(profile: str, *) --- knowit/serializer.py | 10 +++++----- tests/__init__.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/knowit/serializer.py b/knowit/serializer.py index 253172d..7cdb35d 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -9,16 +9,16 @@ from knowit.units import units -def format_property(context, o): +def format_property(profile: str, o): """Convert properties to string.""" if isinstance(o, timedelta): - return format_duration(o, context['profile']) + return format_duration(o, profile) if isinstance(o, babelfish.language.Language): - return format_language(o, context['profile']) + return format_language(o, profile) if hasattr(o, 'units'): - return format_quantity(o, context['profile']) + return format_quantity(o, profile) return str(o) @@ -29,7 +29,7 @@ class StringEncoder(json.JSONEncoder): """String json encoder.""" def default(self, o): - return format_property(context, o) + return format_property(context['profile'], o) return StringEncoder diff --git a/tests/__init__.py b/tests/__init__.py index 90e3c9b..3f935dc 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -276,7 +276,7 @@ def check_equals(expected, actual, different, options, prefix=''): check_mapping_equals(expected, actual, different=different, options=options, prefix=prefix) elif is_iterable(expected): check_sequence_equals(expected, actual, different=different, options=options, prefix=prefix) - elif format_property(options, expected) != format_property(options, actual): + elif format_property(options['profile'], expected) != format_property(options['profile'], actual): different.append((prefix, expected, actual)) From 70bb011f6d7748e7415827d1374669571333e29b Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 01:49:33 -0400 Subject: [PATCH 057/102] Add type-hints --- knowit/properties/audio/channels.py | 11 ++++++----- knowit/properties/audio/codec.py | 2 +- knowit/properties/subtitle/format.py | 2 +- knowit/properties/video/codec.py | 2 +- knowit/property.py | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py index 4d1fd5d..ccf6cd5 100644 --- a/knowit/properties/audio/channels.py +++ b/knowit/properties/audio/channels.py @@ -1,22 +1,23 @@ +import typing from knowit.property import Property -class AudioChannels(Property): +class AudioChannels(Property[int]): """Audio Channels property.""" ignored = { 'object based', # Dolby Atmos } - def handle(self, value, context): + def handle(self, value, context) -> typing.Optional[int]: """Handle audio channels.""" if isinstance(value, int): return value - v = value.lower() - if v not in self.ignored: + if value.lower() not in self.ignored: try: - return int(v) + return int(value) except ValueError: self.report(value, context) + return None diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index 297ebc8..6628c09 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -3,7 +3,7 @@ from knowit.property import Configurable -class AudioCodec(Configurable): +class AudioCodec(Configurable[str]): """Audio codec property.""" @classmethod diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle/format.py index d59c3fb..e7f8058 100644 --- a/knowit/properties/subtitle/format.py +++ b/knowit/properties/subtitle/format.py @@ -2,7 +2,7 @@ from knowit.property import Configurable -class SubtitleFormat(Configurable): +class SubtitleFormat(Configurable[str]): """Subtitle Format property.""" @classmethod diff --git a/knowit/properties/video/codec.py b/knowit/properties/video/codec.py index 7b33b3f..f4dabe3 100644 --- a/knowit/properties/video/codec.py +++ b/knowit/properties/video/codec.py @@ -2,7 +2,7 @@ from knowit.property import Configurable -class VideoCodec(Configurable): +class VideoCodec(Configurable[str]): """Video Codec handler.""" @classmethod diff --git a/knowit/property.py b/knowit/property.py index 46eeb0a..467c237 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -71,7 +71,7 @@ def _deduplicate(cls, value: str) -> str: return values[0] return value - def handle(self, value: T, context: typing.MutableMapping) -> T: + def handle(self, value: T, context: typing.MutableMapping) -> typing.Optional[T]: """Return the value without any modification.""" return value From 141d862a2eb7d1db809292cd17589680e72a1227 Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 17:12:23 -0400 Subject: [PATCH 058/102] Refactor videopath -> video_path --- knowit/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/knowit/__main__.py b/knowit/__main__.py index 6c4d53d..6a415b2 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -171,14 +171,14 @@ def main(args: typing.List[str] = None) -> None: if paths: report: typing.MutableMapping[str, str] = {} - for i, videopath in enumerate(paths): + for i, video_path in enumerate(paths): try: context = dict(vars(options)) if options.report: context['report'] = report else: del context['report'] - knowit(videopath, options, context) + knowit(video_path, options, context) except ProviderError: logger.exception('Error when processing video') except OSError: From fd05a513978c1e5a95f4159d314e53d7929776d9 Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 17:38:10 -0400 Subject: [PATCH 059/102] Refactor to reduce nesting Refactor to reduce nesting --- knowit/__main__.py | 113 +++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 49 deletions(-) diff --git a/knowit/__main__.py b/knowit/__main__.py index 6a415b2..92f9cf9 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -131,28 +131,41 @@ def knowit( return info +def _as_yaml( + info: typing.Mapping[str, typing.Any], + context: typing.Mapping, +) -> str: + """Convert info to string using YAML format.""" + data = {info['path']: info} if 'path' in info else info + return yaml.dump( + data, + Dumper=get_yaml_dumper(context), + default_flow_style=False, + allow_unicode=True, + ) + + +def _as_json( + info: typing.Mapping[str, typing.Any], + context: typing.Mapping, +) -> str: + """Convert info to string using JSON format.""" + return json.dumps( + info, + cls=get_json_encoder(context), + indent=4, + ensure_ascii=False, + ) + + def dump( info: typing.Mapping[str, typing.Any], options: argparse.Namespace, context: typing.Mapping, ) -> str: """Convert info to string using json or yaml format.""" - if options.yaml: - data = {info['path']: info} if 'path' in info else info - result = yaml.dump( - data, - Dumper=get_yaml_dumper(context), - default_flow_style=False, - allow_unicode=True, - ) - else: - result = json.dumps( - info, - cls=get_json_encoder(context), - indent=4, - ensure_ascii=False, - ) - return result + convert = _as_yaml if options.yaml else _as_json + return convert(info, context) def main(args: typing.List[str] = None) -> None: @@ -169,40 +182,42 @@ def main(args: typing.List[str] = None) -> None: paths = recurse_paths(options.videopath) - if paths: - report: typing.MutableMapping[str, str] = {} - for i, video_path in enumerate(paths): - try: - context = dict(vars(options)) - if options.report: - context['report'] = report - else: - del context['report'] - knowit(video_path, options, context) - except ProviderError: - logger.exception('Error when processing video') - except OSError: - logger.exception('OS error when processing video') - except UnicodeError: - logger.exception('Character encoding error when processing video') - except api.KnowitException as e: - logger.error(e) - if options.report and i % 20 == 19 and report: - console.info('Unknown values so far:') - console.info(dump(report, options, vars(options))) - - if options.report: - if report: - console.info('Knowit %s found unknown values:', __version__) - console.info(dump(report, options, vars(options))) - console.info('Please report them at %s', __url__) + if not paths: + if options.version: + console.info(api.debug_info()) + else: + argument_parser.print_help() + return + + report: typing.MutableMapping[str, str] = {} + for i, video_path in enumerate(paths): + try: + context = dict(vars(options)) + if options.report: + context['report'] = report else: - console.info('Knowit %s knows everything. :-)', __version__) - - elif options.version: - console.info(api.debug_info()) - else: - argument_parser.print_help() + del context['report'] + knowit(video_path, options, context) + except ProviderError: + logger.exception('Error when processing video') + except OSError: + logger.exception('OS error when processing video') + except UnicodeError: + logger.exception('Character encoding error when processing video') + except api.KnowitException as e: + logger.error(e) + + if options.report and i % 20 == 19 and report: + console.info('Unknown values so far:') + console.info(dump(report, options, vars(options))) + + if options.report: + if report: + console.info('Knowit %s found unknown values:', __version__) + console.info(dump(report, options, vars(options))) + console.info('Please report them at %s', __url__) + else: + console.info('Knowit %s knows everything. :-)', __version__) if __name__ == '__main__': From 123feb8f8e379e79c23a6d8ccf8f5f6171140f55 Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 22:44:59 -0400 Subject: [PATCH 060/102] Refactor dump -> dumps since dumping data to a string. --- knowit/__main__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/knowit/__main__.py b/knowit/__main__.py index 92f9cf9..25ddf89 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -127,7 +127,7 @@ def knowit( info = api.know(video_path, context) if not options.report: console.info('Knowit %s found: ', __version__) - console.info(dump(info, options, context)) + console.info(dumps(info, options, context)) return info @@ -158,7 +158,7 @@ def _as_json( ) -def dump( +def dumps( info: typing.Mapping[str, typing.Any], options: argparse.Namespace, context: typing.Mapping, @@ -209,12 +209,12 @@ def main(args: typing.List[str] = None) -> None: if options.report and i % 20 == 19 and report: console.info('Unknown values so far:') - console.info(dump(report, options, vars(options))) + console.info(dumps(report, options, vars(options))) if options.report: if report: console.info('Knowit %s found unknown values:', __version__) - console.info(dump(report, options, vars(options))) + console.info(dumps(report, options, vars(options))) console.info('Please report them at %s', __url__) else: console.info('Knowit %s knows everything. :-)', __version__) From 3773ff291b47060f45909605e58f78eff72b4c4f Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 22:47:00 -0400 Subject: [PATCH 061/102] Fix import order --- knowit/providers/mkvmerge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/providers/mkvmerge.py b/knowit/providers/mkvmerge.py index ba67d14..dbde316 100644 --- a/knowit/providers/mkvmerge.py +++ b/knowit/providers/mkvmerge.py @@ -28,7 +28,7 @@ ) from knowit.serializer import get_json_encoder from knowit.units import units -from knowit.utils import detect_os, define_candidate +from knowit.utils import define_candidate, detect_os logger = getLogger(__name__) logger.addHandler(NullHandler()) From c3d20f0f394063abe7e149d9de41aa3ac0ab91f1 Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 22:47:27 -0400 Subject: [PATCH 062/102] Fix multiple spaces after ',' --- knowit/rules/alternative.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/rules/alternative.py b/knowit/rules/alternative.py index 796e1fd..7c1e1dd 100644 --- a/knowit/rules/alternative.py +++ b/knowit/rules/alternative.py @@ -5,7 +5,7 @@ class AlternativeRule(Rule): """Alternative rule.""" - def __init__(self, name, prop_name: str, **kwargs): + def __init__(self, name, prop_name: str, **kwargs): super().__init__(name, **kwargs) self.prop_name = prop_name From a0d853b6f867f76d952e345f24098644aece530b Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 22:49:12 -0400 Subject: [PATCH 063/102] Add docstring --- knowit/properties/duration.py | 1 + knowit/rules/alternative.py | 1 + 2 files changed, 2 insertions(+) diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 18fa3b4..1e1d11d 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -15,6 +15,7 @@ class Duration(Property): r'(?P\d{3})?\d*)?') def __init__(self, name: str, resolution=1, *args, **kwargs): + """Initialize a Duration.""" super().__init__(name, *args, **kwargs) self.resolution = resolution diff --git a/knowit/rules/alternative.py b/knowit/rules/alternative.py index 7c1e1dd..44bcb66 100644 --- a/knowit/rules/alternative.py +++ b/knowit/rules/alternative.py @@ -6,6 +6,7 @@ class AlternativeRule(Rule): """Alternative rule.""" def __init__(self, name, prop_name: str, **kwargs): + """Initialize an AlternativeRule.""" super().__init__(name, **kwargs) self.prop_name = prop_name From 0f56d2c3f236bbb19adf9ee08f617cdd70779971 Mon Sep 17 00:00:00 2001 From: Labrys Date: Thu, 25 Mar 2021 22:49:27 -0400 Subject: [PATCH 064/102] Add type-hint --- knowit/properties/duration.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 1e1d11d..9dea092 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -1,11 +1,10 @@ - import re from datetime import timedelta from knowit.property import Property -class Duration(Property): +class Duration(Property[timedelta]): """Duration property.""" duration_re = re.compile(r'(?P\d{1,2}):' From 94a9d8e3bc2a7fe47652f45a740660120bea7288 Mon Sep 17 00:00:00 2001 From: Labrys Date: Fri, 26 Mar 2021 06:28:23 -0400 Subject: [PATCH 065/102] Fix type-hint --- knowit/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/utils.py b/knowit/utils.py index 06e779f..1e06a64 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -85,7 +85,7 @@ def define_candidate( def build_candidates( - locations: typing.Iterable[str], + locations: typing.Iterable[typing.Optional[str]], names: typing.Iterable[str], ) -> typing.Generator[str, None, None]: """Build candidate names.""" From 9d49d3ec95d1223fb7f4b6e62264b3e1fd2aeef9 Mon Sep 17 00:00:00 2001 From: Labrys Date: Fri, 26 Mar 2021 06:28:51 -0400 Subject: [PATCH 066/102] Refactor units --- knowit/units.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/knowit/units.py b/knowit/units.py index 5091984..73ec16a 100644 --- a/knowit/units.py +++ b/knowit/units.py @@ -1,22 +1,32 @@ +import typing +try: + import pint +except ImportError: + pint = False -def _build_unit_registry(): - try: - from pint import UnitRegistry - registry = UnitRegistry() - registry.define('FPS = 1 * hertz') - except ImportError: - class NoUnitRegistry: +class NullRegistry: + """A NullRegistry that masquerades as a pint.UnitRegistry.""" + + def __init__(self): + """Initialize a null registry.""" - def __init__(self): - pass + def __getattr__(self, item: typing.Any) -> int: + """Return a Scalar 1 to simulate a unit.""" + return 1 - def __getattr__(self, item): - return 1 + def __bool__(self): + """Return False since a NullRegistry is not a pint.UnitRegistry.""" + return False - registry = NoUnitRegistry() + def define(self, *args, **kwargs): + """Pretend to add unit to the registry.""" + +def _build_unit_registry(): + registry = pint.UnitRegistry() if pint else NullRegistry() + registry.define('FPS = 1 * hertz') return registry From acdb916946153977eabd22f8f314593677e22388 Mon Sep 17 00:00:00 2001 From: Labrys Date: Fri, 26 Mar 2021 06:44:12 -0400 Subject: [PATCH 067/102] Add tests for units --- tests/test_units.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test_units.py diff --git a/tests/test_units.py b/tests/test_units.py new file mode 100644 index 0000000..6293ab8 --- /dev/null +++ b/tests/test_units.py @@ -0,0 +1,17 @@ +from knowit.units import NullRegistry + + +def test_null_registry_is_falsey(): + registry = NullRegistry() + assert not registry + + +def test_null_registry_can_define(): + registry = NullRegistry() + registry.define('FPS = 1 * hertz') + + +def test_null_registry_attribute_is_a_scalar_1(): + registry = NullRegistry() + assert registry.fps == 1 + assert registry.some_attribute == 1 From 04b17e9615d7571b75836e19ce37db3773f62d43 Mon Sep 17 00:00:00 2001 From: Labrys Date: Fri, 26 Mar 2021 07:01:59 -0400 Subject: [PATCH 068/102] Add tests for provider --- tests/test_provider.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/test_provider.py diff --git a/tests/test_provider.py b/tests/test_provider.py new file mode 100644 index 0000000..9d48ea8 --- /dev/null +++ b/tests/test_provider.py @@ -0,0 +1,15 @@ +import pytest + +from knowit.provider import Provider +from knowit.units import units + + +@pytest.mark.parametrize( + 'frame_rate', [ + pytest.param(3.4 * units.fps, id='Frame rate with magnitude'), + pytest.param(1, id='Frame rate without magnitude'), + ], +) +def test_provider_validate_track_frame_rate(frame_rate): + track = {'frame_rate': 0} + Provider._validate_track('video', track) From da1c49d50678cf64eb0a9c05e0908c6829ea6a51 Mon Sep 17 00:00:00 2001 From: Labrys Date: Fri, 26 Mar 2021 07:20:19 -0400 Subject: [PATCH 069/102] Refactor _format_quantity --- knowit/serializer.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/knowit/serializer.py b/knowit/serializer.py index 7cdb35d..da4911d 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -146,15 +146,20 @@ def _format_quantity( binary: bool = False, precision: int = 2, ) -> str: - fmt_pattern = '{value:3.%sf} {prefix}{affix}{unit}' % precision - factor = 1024. if binary else 1000. - binary_affix = 'i' if binary else '' + if binary: + factor = 1024 + affix = 'i' + else: + factor = 1000 + affix = '' for prefix in ('', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'): if abs(num) < factor: - return fmt_pattern.format(value=num, prefix=prefix, affix=binary_affix, unit=unit) + break num /= factor + else: + prefix = 'Y' - return fmt_pattern.format(value=num, prefix='Y', affix=binary_affix, unit=unit) + return f'{num:3.{precision}f} {prefix}{affix}{unit}' YAMLLoader = get_yaml_loader() From d053836307a646e1fc13eaf105f4e416c29674d7 Mon Sep 17 00:00:00 2001 From: Labrys Date: Fri, 26 Mar 2021 07:40:59 -0400 Subject: [PATCH 070/102] Add tests for video profile tier --- tests/test_video_profile.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/test_video_profile.py diff --git a/tests/test_video_profile.py b/tests/test_video_profile.py new file mode 100644 index 0000000..a15c0cf --- /dev/null +++ b/tests/test_video_profile.py @@ -0,0 +1,5 @@ +from knowit.properties.video import VideoProfileTier + + +def test_video_profile_tier_extract_key_when_no_tier(): + assert VideoProfileTier._extract_key('') is False From d8469ac46f9f933198258111df8781a5974bdfc5 Mon Sep 17 00:00:00 2001 From: Labrys Date: Fri, 26 Mar 2021 08:23:27 -0400 Subject: [PATCH 071/102] Add type-hints --- knowit/core.py | 2 +- knowit/properties/audio/bitratemode.py | 2 +- knowit/properties/audio/channels.py | 2 +- knowit/properties/audio/compression.py | 2 +- knowit/properties/audio/profile.py | 2 +- knowit/properties/basic.py | 9 ++++++--- knowit/properties/duration.py | 5 +++-- knowit/properties/language.py | 5 +++-- knowit/properties/video/profile.py | 6 +++--- knowit/properties/video/ratio.py | 4 ++-- knowit/properties/video/scantype.py | 2 +- knowit/rule.py | 10 +++++++--- knowit/rules/alternative.py | 2 +- 13 files changed, 31 insertions(+), 22 deletions(-) diff --git a/knowit/core.py b/knowit/core.py index aa3cfe4..645d626 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -26,7 +26,7 @@ def description(self) -> str: """Rule description.""" return self._description or self.name - def report(self, value: T, context: typing.MutableMapping) -> None: + def report(self, value: typing.Union[str, T], context: typing.MutableMapping) -> None: """Report unknown value.""" if not value or not self.reportable: return diff --git a/knowit/properties/audio/bitratemode.py b/knowit/properties/audio/bitratemode.py index 4771d2a..838a996 100644 --- a/knowit/properties/audio/bitratemode.py +++ b/knowit/properties/audio/bitratemode.py @@ -2,5 +2,5 @@ from knowit.property import Configurable -class BitRateMode(Configurable): +class BitRateMode(Configurable[str]): """Bit Rate mode property.""" diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py index ccf6cd5..ac97100 100644 --- a/knowit/properties/audio/channels.py +++ b/knowit/properties/audio/channels.py @@ -10,7 +10,7 @@ class AudioChannels(Property[int]): 'object based', # Dolby Atmos } - def handle(self, value, context) -> typing.Optional[int]: + def handle(self, value: typing.Union[int, str], context: typing.MutableMapping) -> typing.Optional[int]: """Handle audio channels.""" if isinstance(value, int): return value diff --git a/knowit/properties/audio/compression.py b/knowit/properties/audio/compression.py index 2317793..30253a9 100644 --- a/knowit/properties/audio/compression.py +++ b/knowit/properties/audio/compression.py @@ -2,5 +2,5 @@ from knowit.property import Configurable -class AudioCompression(Configurable): +class AudioCompression(Configurable[str]): """Audio Compression property.""" diff --git a/knowit/properties/audio/profile.py b/knowit/properties/audio/profile.py index 4ef833a..4dea05e 100644 --- a/knowit/properties/audio/profile.py +++ b/knowit/properties/audio/profile.py @@ -2,5 +2,5 @@ from knowit.property import Configurable -class AudioProfile(Configurable): +class AudioProfile(Configurable[str]): """Audio profile property.""" diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index 23c3b52..f9d9870 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -1,17 +1,20 @@ +import typing from knowit.property import Property +T = typing.TypeVar('T') -class Basic(Property): + +class Basic(Property[T]): """Basic property to handle int, float and other basic types.""" - def __init__(self, name, data_type, allow_fallback=False, **kwargs): + def __init__(self, name: str, data_type: typing.Type, allow_fallback: bool = False, **kwargs): """Init method.""" super().__init__(name, **kwargs) self.data_type = data_type self.allow_fallback = allow_fallback - def handle(self, value, context): + def handle(self, value, context: typing.MutableMapping): """Handle value.""" if isinstance(value, self.data_type): return value diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 9dea092..158fa51 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -1,4 +1,5 @@ import re +import typing from datetime import timedelta from knowit.property import Property @@ -13,12 +14,12 @@ class Duration(Property[timedelta]): r'(?P\d{3})' r'(?P\d{3})?\d*)?') - def __init__(self, name: str, resolution=1, *args, **kwargs): + def __init__(self, name: str, resolution: float = 1, *args, **kwargs): """Initialize a Duration.""" super().__init__(name, *args, **kwargs) self.resolution = resolution - def handle(self, value, context): + def handle(self, value, context: typing.MutableMapping): """Return duration as timedelta.""" if isinstance(value, timedelta): return value diff --git a/knowit/properties/language.py b/knowit/properties/language.py index 6c04627..06f3763 100644 --- a/knowit/properties/language.py +++ b/knowit/properties/language.py @@ -1,13 +1,14 @@ +import typing import babelfish from knowit.property import Property -class Language(Property): +class Language(Property[babelfish.Language]): """Language property.""" - def handle(self, value, context): + def handle(self, value, context: typing.MutableMapping): """Handle languages.""" try: if len(value) == 3: diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py index 55cc3fe..8f02cef 100644 --- a/knowit/properties/video/profile.py +++ b/knowit/properties/video/profile.py @@ -3,7 +3,7 @@ from knowit.property import Configurable -class VideoProfile(Configurable): +class VideoProfile(Configurable[str]): """Video Profile property.""" @classmethod @@ -11,7 +11,7 @@ def _extract_key(cls, value) -> str: return value.upper().split('@')[0] -class VideoProfileLevel(Configurable): +class VideoProfileLevel(Configurable[str]): """Video Profile Level property.""" @classmethod @@ -25,7 +25,7 @@ def _extract_key(cls, value) -> typing.Union[str, bool]: return False -class VideoProfileTier(Configurable): +class VideoProfileTier(Configurable[str]): """Video Profile Tier property.""" @classmethod diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index 9c40719..e4d903a 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -5,10 +5,10 @@ from knowit.property import Property -class Ratio(Property): +class Ratio(Property[float]): """Ratio property.""" - def __init__(self, name, unit=None, **kwargs): + def __init__(self, name: str, unit=None, **kwargs): """Initialize the object.""" super().__init__(name, **kwargs) self.unit = unit diff --git a/knowit/properties/video/scantype.py b/knowit/properties/video/scantype.py index 1367796..2021a3f 100644 --- a/knowit/properties/video/scantype.py +++ b/knowit/properties/video/scantype.py @@ -2,5 +2,5 @@ from knowit.property import Configurable -class ScanType(Configurable): +class ScanType(Configurable[str]): """Scan Type property.""" diff --git a/knowit/rule.py b/knowit/rule.py index b3e715f..ca01603 100644 --- a/knowit/rule.py +++ b/knowit/rule.py @@ -1,15 +1,19 @@ +import typing from knowit.core import Reportable -class Rule(Reportable): +T = typing.TypeVar('T') + + +class Rule(Reportable[T]): """Rule abstract class.""" - def __init__(self, name, override=False, **kwargs): + def __init__(self, name: str, override=False, **kwargs): """Initialize the object.""" super().__init__(name, **kwargs) self.override = override - def execute(self, props, pv_props, context): + def execute(self, props, pv_props, context: typing.Mapping): """How to execute a rule.""" raise NotImplementedError diff --git a/knowit/rules/alternative.py b/knowit/rules/alternative.py index 44bcb66..ff4716f 100644 --- a/knowit/rules/alternative.py +++ b/knowit/rules/alternative.py @@ -5,7 +5,7 @@ class AlternativeRule(Rule): """Alternative rule.""" - def __init__(self, name, prop_name: str, **kwargs): + def __init__(self, name: str, prop_name: str, **kwargs): """Initialize an AlternativeRule.""" super().__init__(name, **kwargs) self.prop_name = prop_name From 32399406c5c348169a181a6591057fc9cf3f1df4 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sat, 27 Mar 2021 01:09:45 -0400 Subject: [PATCH 072/102] Refactor duration handling --- knowit/properties/duration.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 158fa51..6725e08 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -11,8 +11,8 @@ class Duration(Property[timedelta]): duration_re = re.compile(r'(?P\d{1,2}):' r'(?P\d{1,2}):' r'(?P\d{1,2})(?:\.' - r'(?P\d{3})' - r'(?P\d{3})?\d*)?') + r'(?P\d{3})' + r'(?P\d{3})?\d*)?') def __init__(self, name: str, resolution: float = 1, *args, **kwargs): """Initialize a Duration.""" @@ -30,10 +30,14 @@ def handle(self, value, context: typing.MutableMapping): except ValueError: pass - try: - h, m, s, ms, mc = self.duration_re.match(value).groups('0') - return timedelta(hours=int(h), minutes=int(m), seconds=int(s), milliseconds=int(ms), microseconds=int(mc)) - except ValueError: - pass - - self.report(value, context) + match = self.duration_re.match(value) + if not match: + self.report(value, context) + return None + + params = { + key: int(value) + for key, value in match.groupdict().items() + if value + } + return timedelta(**params) From b0444469d4e6f025bc0edf56ae336dccb8378529 Mon Sep 17 00:00:00 2001 From: Labrys Date: Sat, 27 Mar 2021 01:10:03 -0400 Subject: [PATCH 073/102] Add type-hints --- knowit/provider.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/knowit/provider.py b/knowit/provider.py index 3a3fd7d..e721e78 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -1,8 +1,12 @@ import os +import typing from logging import NullHandler, getLogger +import knowit.config from knowit.properties import Quantity +from knowit.property import Property +from knowit.rule import Rule from knowit.units import units logger = getLogger(__name__) @@ -11,6 +15,12 @@ size_property = Quantity('size', units.byte, description='media size') +PropertyMap = typing.Mapping[str, Property] +PropertyConfig = typing.Mapping[str, PropertyMap] + +RuleMap = typing.Mapping[str, Rule] +RuleConfig = typing.Mapping[str, RuleMap] + class Provider: """Base class for all providers.""" @@ -18,7 +28,12 @@ class Provider: min_fps = 10 max_fps = 200 - def __init__(self, config, mapping, rules=None): + def __init__( + self, + config: knowit.config.Config, + mapping: PropertyConfig, + rules: typing.Optional[RuleConfig] = None, + ): """Init method.""" self.config = config self.mapping = mapping From 96c07f949be1cc303cfe52954279b44901a78999 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 10:41:51 +0100 Subject: [PATCH 074/102] Add support to multiple names in Property. Drop Alternative rule --- knowit/core.py | 6 ++-- knowit/properties/basic.py | 4 +-- knowit/properties/duration.py | 4 +-- knowit/properties/quantity.py | 4 +-- knowit/properties/video/ratio.py | 4 +-- knowit/properties/yesno.py | 4 +-- knowit/property.py | 48 +++++++++++++++++--------------- knowit/provider.py | 2 +- knowit/providers/enzyme.py | 12 ++++---- knowit/providers/ffmpeg.py | 24 ++++++++-------- knowit/providers/mediainfo.py | 47 +++++++++++++------------------ knowit/providers/mkvmerge.py | 21 ++++++-------- knowit/rules/__init__.py | 1 - knowit/rules/alternative.py | 16 ----------- knowit/rules/language.py | 3 -- 15 files changed, 86 insertions(+), 114 deletions(-) delete mode 100644 knowit/rules/alternative.py diff --git a/knowit/core.py b/knowit/core.py index 645d626..a81ee03 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -12,19 +12,19 @@ class Reportable(typing.Generic[T]): def __init__( self, - name: str, + *args: str, description: typing.Optional[str] = None, reportable: bool = True, ): """Initialize the object.""" - self.name = name + self.names = args self._description = description self.reportable = reportable @property def description(self) -> str: """Rule description.""" - return self._description or self.name + return self._description or '|'.join(self.names) def report(self, value: typing.Union[str, T], context: typing.MutableMapping) -> None: """Report unknown value.""" diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index f9d9870..462c33e 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -8,9 +8,9 @@ class Basic(Property[T]): """Basic property to handle int, float and other basic types.""" - def __init__(self, name: str, data_type: typing.Type, allow_fallback: bool = False, **kwargs): + def __init__(self, *args: str, data_type: typing.Type, allow_fallback: bool = False, **kwargs): """Init method.""" - super().__init__(name, **kwargs) + super().__init__(*args, **kwargs) self.data_type = data_type self.allow_fallback = allow_fallback diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 6725e08..4f3d364 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -14,9 +14,9 @@ class Duration(Property[timedelta]): r'(?P\d{3})' r'(?P\d{3})?\d*)?') - def __init__(self, name: str, resolution: float = 1, *args, **kwargs): + def __init__(self, *args: str, resolution: float = 1, **kwargs): """Initialize a Duration.""" - super().__init__(name, *args, **kwargs) + super().__init__(*args, **kwargs) self.resolution = resolution def handle(self, value, context: typing.MutableMapping): diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index 64c6fc2..b1c2a3b 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -5,9 +5,9 @@ class Quantity(Property): """Quantity is a property with unit.""" - def __init__(self, name, unit, data_type=int, **kwargs): + def __init__(self, *args: str, unit, data_type=int, **kwargs): """Init method.""" - super().__init__(name, **kwargs) + super().__init__(*args, **kwargs) self.unit = unit self.data_type = data_type diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index e4d903a..34930f7 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -8,9 +8,9 @@ class Ratio(Property[float]): """Ratio property.""" - def __init__(self, name: str, unit=None, **kwargs): + def __init__(self, *args: str, unit=None, **kwargs): """Initialize the object.""" - super().__init__(name, **kwargs) + super().__init__(*args, **kwargs) self.unit = unit ratio_re = re.compile(r'(?P\d+)[:/](?P\d+)') diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index c8ad67d..dbb6864 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -7,9 +7,9 @@ class YesNo(Property): mapping = ('yes', 'true', '1') - def __init__(self, name, yes=True, no=False, hide_value=None, **kwargs): + def __init__(self, *args: str, yes=True, no=False, hide_value=None, **kwargs): """Init method.""" - super().__init__(name, **kwargs) + super().__init__(*args, **kwargs) self.yes = yes self.no = no self.hide_value = hide_value diff --git a/knowit/property.py b/knowit/property.py index 467c237..8bade18 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -21,7 +21,7 @@ class Property(Reportable[T]): def __init__( self, - name: str, + *args: str, default: typing.Optional[T] = None, private: bool = False, description: typing.Optional[str] = None, @@ -29,7 +29,7 @@ def __init__( **kwargs, ): """Init method.""" - super().__init__(name, description, **kwargs) + super().__init__(*args, description=description, **kwargs) self.default = default self.private = private # Used to detect duplicated values. e.g.: en / en or High@L4.0 / High@L4.0 or Progressive / Progressive @@ -41,28 +41,29 @@ def extract_value( context: typing.MutableMapping, ) -> typing.Optional[T]: """Extract the property value from a given track.""" - names = self.name.split('.') - value = track.get(names[0], {}).get(names[1]) if len(names) == 2 else track.get(self.name) - if value is None: - if self.default is None: - return None + for name in self.names: + names = name.split('.') + value = track.get(names[0], {}).get(names[1]) if len(names) == 2 else track.get(name) + if value is None: + if self.default is None: + continue - value = self.default + value = self.default - if isinstance(value, bytes): - value = value.decode() + if isinstance(value, bytes): + value = value.decode() - if isinstance(value, str): - value = value.translate(_visible_chars_table).strip() - if _is_unknown(value): - return None - value = self._deduplicate(value) + if isinstance(value, str): + value = value.translate(_visible_chars_table).strip() + if _is_unknown(value): + continue + value = self._deduplicate(value) - result = self.handle(value, context) - if not _is_unknown(result): - return result - else: - return None + result = self.handle(value, context) + if result is not None and not _is_unknown(result): + return result + + return None @classmethod def _deduplicate(cls, value: str) -> str: @@ -79,7 +80,7 @@ def handle(self, value: T, context: typing.MutableMapping) -> typing.Optional[T] class Configurable(Property[T]): """Configurable property where values are in a config mapping.""" - def __init__(self, config: typing.Mapping[str, typing.Mapping], *args, **kwargs): + def __init__(self, config: typing.Mapping[str, typing.Mapping], *args: str, **kwargs): """Init method.""" super().__init__(*args, **kwargs) self.mapping = getattr(config, self.__class__.__name__) @@ -128,9 +129,10 @@ def handle(self, value, context): class MultiValue(Property): """Property with multiple values.""" - def __init__(self, prop=None, delimiter='/', single=False, handler=None, name=None, **kwargs): + def __init__(self, prop: typing.Optional[Property[typing.Any]] = None, delimiter='/', single=False, + handler=None, name=None, **kwargs): """Init method.""" - super().__init__(prop.name if prop else name, **kwargs) + super().__init__(*(prop.names if prop else (name,)), **kwargs) self.prop = prop self.delimiter = delimiter self.single = single diff --git a/knowit/provider.py b/knowit/provider.py index e721e78..56ce5d0 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -13,7 +13,7 @@ logger.addHandler(NullHandler()) -size_property = Quantity('size', units.byte, description='media size') +size_property = Quantity('size', unit=units.byte, description='media size') PropertyMap = typing.Mapping[str, Property] PropertyConfig = typing.Mapping[str, PropertyMap] diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index 632e6d7..80a426b 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -45,11 +45,11 @@ def __init__(self, config, *args, **kwargs): 'duration': Duration('duration', description='media duration'), }, 'video': { - 'id': Basic('number', int, description='video track number'), + 'id': Basic('number', data_type=int, description='video track number'), 'name': Property('name', description='video track name'), 'language': Language('language', description='video language'), - 'width': Quantity('width', units.pixel), - 'height': Quantity('height', units.pixel), + 'width': Quantity('width', unit=units.pixel), + 'height': Quantity('height', unit=units.pixel), 'scan_type': YesNo('interlaced', yes='Interlaced', no='Progressive', default='Progressive', description='video scan type'), 'resolution': None, # populated with ResolutionRule @@ -60,18 +60,18 @@ def __init__(self, config, *args, **kwargs): 'enabled': YesNo('enabled', hide_value=True, description='video track enabled'), }, 'audio': { - 'id': Basic('number', int, description='audio track number'), + 'id': Basic('number', data_type=int, description='audio track number'), 'name': Property('name', description='audio track name'), 'language': Language('language', description='audio language'), 'codec': AudioCodec(config, 'codec_id', description='audio codec'), - 'channels_count': Basic('channels', int, description='audio channels count'), + 'channels_count': Basic('channels', data_type=int, description='audio channels count'), 'channels': None, # populated with AudioChannelsRule 'forced': YesNo('forced', hide_value=False, description='audio track forced'), 'default': YesNo('default', hide_value=False, description='audio track default'), 'enabled': YesNo('enabled', hide_value=True, description='audio track enabled'), }, 'subtitle': { - 'id': Basic('number', int, description='subtitle track number'), + 'id': Basic('number', data_type=int, description='subtitle track number'), 'name': Property('name', description='subtitle track name'), 'language': Language('language', description='subtitle language'), 'hearing_impaired': None, # populated with HearingImpairedRule diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index b8edd2f..cf4e149 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -140,24 +140,24 @@ def __init__(self, config, suggested_path=None): 'title': Property('tags.title', description='media title'), 'path': Property('filename', description='media path'), 'duration': Duration('duration', description='media duration'), - 'size': Quantity('size', units.byte, description='media size'), - 'bit_rate': Quantity('bit_rate', units.bps, description='media bit rate'), + 'size': Quantity('size', unit=units.byte, description='media size'), + 'bit_rate': Quantity('bit_rate', unit=units.bps, description='media bit rate'), }, 'video': { - 'id': Basic('index', int, allow_fallback=True, description='video track number'), + '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'), - 'width': Quantity('width', units.pixel), - 'height': Quantity('height', units.pixel), + 'width': Quantity('width', unit=units.pixel), + 'height': Quantity('height', unit=units.pixel), 'scan_type': ScanType(config, 'field_order', default='Progressive', description='video scan type'), 'aspect_ratio': Ratio('display_aspect_ratio', description='display aspect ratio'), 'pixel_aspect_ratio': Ratio('sample_aspect_ratio', description='pixel aspect ratio'), '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', units.bps, description='video bit rate'), - 'bit_depth': Quantity('bits_per_raw_sample', units.bit, description='video bit depth'), + 'bit_rate': Quantity('bit_rate', 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'), 'profile_level': VideoProfileLevel(config, 'level', description='video codec profile level'), @@ -166,7 +166,7 @@ def __init__(self, config, suggested_path=None): 'default': YesNo('disposition.default', hide_value=False, description='video track default'), }, 'audio': { - 'id': Basic('index', int, allow_fallback=True, description='audio track number'), + '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'), @@ -175,14 +175,14 @@ def __init__(self, config, suggested_path=None): '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', units.bit, description='audio bit depth'), - 'bit_rate': Quantity('bit_rate', units.bps, description='audio bit rate'), - 'sampling_rate': Quantity('sample_rate', units.Hz, description='audio sampling rate'), + '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'), + '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'), }, 'subtitle': { - 'id': Basic('index', int, allow_fallback=True, description='subtitle track number'), + 'id': Basic('index', data_type=int, allow_fallback=True, description='subtitle track number'), 'name': Property('tags.title', description='subtitle track name'), 'language': Language('tags.language', description='subtitle language'), 'hearing_impaired': YesNo('disposition.hearing_impaired', diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index b8b1a1f..e431589 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -36,7 +36,6 @@ Provider, ) from ..rules import ( - AlternativeRule, AtmosRule, AudioChannelsRule, ClosedCaptionRule, @@ -184,25 +183,25 @@ def __init__(self, config, suggested_path): 'title': Property('Title', description='media title'), 'path': Property('CompleteName', description='media path'), 'duration': Duration('Duration', resolution=1000000, description='media duration'), - 'size': Quantity('FileSize', units.byte, description='media size'), - 'bit_rate': Quantity('OverallBitRate', units.bps, description='media bit rate'), + 'size': Quantity('FileSize', unit=units.byte, description='media size'), + 'bit_rate': Quantity('OverallBitRate', unit=units.bps, description='media bit rate'), }, 'video': { - 'id': Basic('ID', int, allow_fallback=True, description='video track number'), + 'id': Basic('ID', data_type=int, allow_fallback=True, description='video track number'), 'name': Property('Title', description='video track name'), 'language': Language('Language', description='video language'), 'duration': Duration('Duration', resolution=1000000, description='video duration'), - 'size': Quantity('StreamSize', units.byte, description='video stream size'), - 'width': Quantity('Width', units.pixel), - 'height': Quantity('Height', units.pixel), + 'size': Quantity('StreamSize', unit=units.byte, description='video stream size'), + 'width': Quantity('Width', unit=units.pixel), + 'height': Quantity('Height', unit=units.pixel), 'scan_type': ScanType(config, 'ScanType', default='Progressive', description='video scan type'), - 'aspect_ratio': Basic('DisplayAspectRatio', float, description='display aspect ratio'), - 'pixel_aspect_ratio': Basic('PixelAspectRatio', float, description='pixel aspect ratio'), + 'aspect_ratio': Basic('DisplayAspectRatio', data_type=float, description='display aspect ratio'), + 'pixel_aspect_ratio': Basic('PixelAspectRatio', data_type=float, description='pixel aspect ratio'), 'resolution': None, # populated with ResolutionRule - 'frame_rate': Quantity('FrameRate', units.FPS, float, description='video frame rate'), + 'frame_rate': Quantity('FrameRate', unit=units.FPS, data_type=float, description='video frame rate'), # frame_rate_mode - 'bit_rate': Quantity('BitRate', units.bps, description='video bit rate'), - 'bit_depth': Quantity('BitDepth', units.bit, description='video bit depth'), + 'bit_rate': Quantity('BitRate', unit=units.bps, description='video bit rate'), + 'bit_depth': Quantity('BitDepth', unit=units.bit, description='video bit depth'), 'codec': VideoCodec(config, 'CodecID', description='video codec'), 'profile': VideoProfile(config, 'Format_Profile', description='video codec profile'), 'profile_level': Property('Format_Level', description='video codec profile level'), @@ -213,35 +212,31 @@ def __init__(self, config, suggested_path): 'default': YesNo('Default', hide_value=False, description='video track default'), }, 'audio': { - 'id': Basic('ID', int, allow_fallback=True, description='audio track number'), + 'id': Basic('ID', data_type=int, allow_fallback=True, description='audio track number'), 'name': Property('Title', description='audio track name'), 'language': Language('Language', description='audio language'), 'duration': Duration('Duration', resolution=1000000, description='audio duration'), - 'size': Quantity('StreamSize', units.byte, description='audio stream size'), + 'size': Quantity('StreamSize', unit=units.byte, description='audio stream size'), 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), - 'profile': MultiValue(AudioProfile(config, 'Format_Profile', + 'profile': MultiValue(AudioProfile(config, 'Format_Profile', 'Format_AdditionalFeatures', description='audio codec profile'), delimiter=' / '), - '_profile': MultiValue(AudioProfile(config, 'Format_AdditionalFeatures', - description='audio codec additional features'), - delimiter=' / ', private=True), - 'channels_count': MultiValue(AudioChannels('Channels_Original', description='audio channels count')), - '_channels_count': MultiValue(AudioChannels('Channels', description='audio channels count'), - private=True), + 'channels_count': MultiValue(AudioChannels('Channels_Original', 'Channels', + description='audio channels count')), 'channel_positions': MultiValue(name='ChannelPositions_String2', handler=(lambda x, *args: x), delimiter=' / ', private=True, description='audio channels position'), 'channels': None, # populated with AudioChannelsRule - 'bit_depth': Quantity('BitDepth', units.bit, description='audio bit depth'), - 'bit_rate': MultiValue(Quantity('BitRate', units.bps, description='audio bit rate')), + 'bit_depth': Quantity('BitDepth', unit=units.bit, description='audio bit depth'), + 'bit_rate': MultiValue(Quantity('BitRate', unit=units.bps, description='audio bit rate')), 'bit_rate_mode': MultiValue(BitRateMode(config, 'BitRate_Mode', description='audio bit rate mode')), - 'sampling_rate': MultiValue(Quantity('SamplingRate', units.Hz, description='audio sampling rate')), + 'sampling_rate': MultiValue(Quantity('SamplingRate', unit=units.Hz, description='audio sampling rate')), 'compression': MultiValue(AudioCompression(config, 'Compression_Mode', description='audio compression')), 'forced': YesNo('Forced', hide_value=False, description='audio track forced'), 'default': YesNo('Default', hide_value=False, description='audio track default'), }, 'subtitle': { - 'id': Basic('ID', int, allow_fallback=True, description='subtitle track number'), + 'id': Basic('ID', data_type=int, allow_fallback=True, description='subtitle track number'), 'name': Property('Title', description='subtitle track name'), 'language': Language('Language', description='subtitle language'), 'hearing_impaired': None, # populated with HearingImpairedRule @@ -258,9 +253,7 @@ def __init__(self, config, suggested_path): }, 'audio': { 'language': LanguageRule('audio language'), - 'channels_count': AlternativeRule('audio channels count', 'channels_count', override=True), 'channels': AudioChannelsRule('audio channels'), - 'profile': AlternativeRule('audio codec profile', 'profile', override=True), '_atmosrule': AtmosRule('atmos rule'), '_dtshdrule': DtsHdRule('dts-hd rule'), }, diff --git a/knowit/providers/mkvmerge.py b/knowit/providers/mkvmerge.py index dbde316..41cce2c 100644 --- a/knowit/providers/mkvmerge.py +++ b/knowit/providers/mkvmerge.py @@ -125,12 +125,11 @@ def __init__(self, config, suggested_path=None, *args, **kwargs): 'duration': Duration('duration', resolution=0.001, description='media duration'), }, 'video': { - 'id': Basic('number', int, description='video track number'), + 'id': Basic('number', data_type=int, description='video track number'), 'name': Property('name', description='video track name'), - 'language': Language('language', description='video language'), - 'language_ietf': Language('language_ietf', description='video language', private=True), - 'width': Quantity('width', units.pixel), # TODO: extract resolution - 'height': Quantity('height', units.pixel), + 'language': Language('language_ietf', 'language', description='video language'), + 'width': Quantity('width', unit=units.pixel), # TODO: extract resolution + 'height': Quantity('height', unit=units.pixel), 'scan_type': YesNo('interlaced', yes='Interlaced', no='Progressive', default='Progressive', description='video scan type'), 'resolution': None, # populated with ResolutionRule @@ -141,22 +140,20 @@ def __init__(self, config, suggested_path=None, *args, **kwargs): 'enabled': YesNo('enabled_track', hide_value=True, description='video track enabled'), }, 'audio': { - 'id': Basic('number', int, description='audio track number'), + 'id': Basic('number', data_type=int, description='audio track number'), 'name': Property('name', description='audio track name'), - 'language': Language('language', description='audio language'), - 'language_ietf': Language('language_ietf', description='audio language', private=True), + 'language': Language('language_ietf', 'language', description='audio language'), 'codec': AudioCodec(config, 'codec_id', description='audio codec'), - 'channels_count': Basic('audio_channels', int, description='audio channels count'), + 'channels_count': Basic('audio_channels', data_type=int, description='audio channels count'), 'channels': None, # populated with AudioChannelsRule 'forced': YesNo('forced_track', hide_value=False, description='audio track forced'), 'default': YesNo('default_track', hide_value=False, description='audio track default'), 'enabled': YesNo('enabled_track', hide_value=True, description='audio track enabled'), }, 'subtitle': { - 'id': Basic('number', int, description='subtitle track number'), + 'id': Basic('number', data_type=int, description='subtitle track number'), 'name': Property('name', description='subtitle track name'), - 'language': Language('language', description='subtitle language'), - 'language_ietf': Language('language_ietf', description='subtitle language', private=True), + 'language': Language('language_ietf', 'language', description='subtitle language'), 'hearing_impaired': None, # populated with HearingImpairedRule 'closed_caption': None, # populated with ClosedCaptionRule 'forced': YesNo('forced_track', hide_value=False, description='subtitle track forced'), diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index bdd89fa..8299e49 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -1,5 +1,4 @@ -from knowit.rules.alternative import AlternativeRule from knowit.rules.audio import AtmosRule from knowit.rules.audio import AudioChannelsRule from knowit.rules.audio import AudioCodecRule diff --git a/knowit/rules/alternative.py b/knowit/rules/alternative.py deleted file mode 100644 index ff4716f..0000000 --- a/knowit/rules/alternative.py +++ /dev/null @@ -1,16 +0,0 @@ - -from knowit.rule import Rule - - -class AlternativeRule(Rule): - """Alternative rule.""" - - def __init__(self, name: str, prop_name: str, **kwargs): - """Initialize an AlternativeRule.""" - super().__init__(name, **kwargs) - self.prop_name = prop_name - - def execute(self, props, pv_props, context): - """Execute the rule against properties.""" - if f'_{self.prop_name}' in pv_props and self.prop_name not in props: - return pv_props.get(f'_{self.prop_name}') diff --git a/knowit/rules/language.py b/knowit/rules/language.py index 2bbe622..9a7355a 100644 --- a/knowit/rules/language.py +++ b/knowit/rules/language.py @@ -17,9 +17,6 @@ class LanguageRule(Rule): def execute(self, props, pv_props, context): """Language detection using name.""" - if 'language_ietf' in pv_props: - return pv_props['language_ietf'] - if 'language' in props: return From d384a138ef9ba8ffe201c96e8c6f52a0af867419 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 14:23:24 +0100 Subject: [PATCH 075/102] Switch test expected output to profile=code to be more accurate for languages (country/script) and duration. --- knowit/properties/yesno.py | 21 +- knowit/property.py | 5 +- knowit/providers/enzyme.py | 1 + knowit/providers/mediainfo.py | 2 +- knowit/rules/audio/dtshd.py | 13 +- tests/__init__.py | 8 +- tests/conftest.py | 19 +- ...ts-hd-ma-speaker-mapping-test-file.mkv.yml | 6 +- tests/data/enzyme/test1.mkv.yml | 10 +- tests/data/enzyme/test2.mkv.yml | 12 +- tests/data/enzyme/test3.mkv.yml | 10 +- tests/data/enzyme/test5.mkv.yml | 26 +- tests/data/enzyme/test6.mkv.yml | 10 +- tests/data/enzyme/test7.mkv.yml | 12 +- tests/data/enzyme/test8.mkv.yml | 12 +- ...ts-hd-ma-speaker-mapping-test-file.mkv.yml | 20 +- tests/data/ffmpeg/media_001.mkv.json | 956 +++++++++++++++++ tests/data/ffmpeg/media_001.mkv.yml | 131 +++ tests/data/ffmpeg/test1.mkv.yml | 14 +- tests/data/ffmpeg/test2.mkv.yml | 16 +- tests/data/ffmpeg/test3.mkv.yml | 18 +- tests/data/ffmpeg/test4.mkv.yml | 8 +- .../data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml | 52 +- tests/data/ffmpeg/test5.mkv.yml | 52 +- tests/data/ffmpeg/test6.mkv.yml | 14 +- tests/data/ffmpeg/test7.mkv.yml | 16 +- tests/data/ffmpeg/test8.mkv.yml | 16 +- ...ts-hd-ma-speaker-mapping-test-file.mkv.yml | 28 +- tests/data/mediainfo/test1.mkv.yml | 22 +- tests/data/mediainfo/test2.mkv.yml | 22 +- tests/data/mediainfo/test3.mkv.yml | 26 +- tests/data/mediainfo/test4.mkv.yml | 16 +- tests/data/mediainfo/test5.mkv.yml | 62 +- tests/data/mediainfo/test6.mkv.yml | 22 +- tests/data/mediainfo/test7.mkv.yml | 22 +- tests/data/mediainfo/test8.mkv.yml | 22 +- tests/test_enzyme/test_001.yml | 130 --- tests/test_enzyme/test_002.yml | 13 - tests/test_ffmpeg/test_001.yml | 986 ------------------ 39 files changed, 1425 insertions(+), 1426 deletions(-) create mode 100644 tests/data/ffmpeg/media_001.mkv.json create mode 100644 tests/data/ffmpeg/media_001.mkv.yml delete mode 100644 tests/test_enzyme/test_001.yml delete mode 100644 tests/test_enzyme/test_002.yml delete mode 100644 tests/test_ffmpeg/test_001.yml diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index dbb6864..992de82 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -1,20 +1,27 @@ +import typing -from knowit.property import Property +from knowit.property import Configurable -class YesNo(Property): +class YesNo(Configurable[str]): """Yes or No handler.""" - mapping = ('yes', 'true', '1') + yes_values = ('yes', 'true', '1') - def __init__(self, *args: str, yes=True, no=False, hide_value=None, **kwargs): + def __init__(self, *args: str, yes=True, no=False, hide_value=None, + config: typing.Optional[typing.Mapping[str, typing.Mapping]] = None, + config_key: typing.Optional[str] = None, + **kwargs): """Init method.""" - super().__init__(*args, **kwargs) + super().__init__(config or {}, config_key=config_key, *args, **kwargs) self.yes = yes self.no = no self.hide_value = hide_value def handle(self, value, context): """Handle boolean values.""" - result = self.yes if str(value).lower() in self.mapping else self.no - return result if result != self.hide_value else None + result = self.yes if str(value).lower() in self.yes_values else self.no + if result == self.hide_value: + return None + + return super().handle(result, context) if self.mapping else result diff --git a/knowit/property.py b/knowit/property.py index 8bade18..734d8a7 100644 --- a/knowit/property.py +++ b/knowit/property.py @@ -80,10 +80,11 @@ def handle(self, value: T, context: typing.MutableMapping) -> typing.Optional[T] class Configurable(Property[T]): """Configurable property where values are in a config mapping.""" - def __init__(self, config: typing.Mapping[str, typing.Mapping], *args: str, **kwargs): + def __init__(self, config: typing.Mapping[str, typing.Mapping], *args: str, + config_key: typing.Optional[str] = None, **kwargs): """Init method.""" super().__init__(*args, **kwargs) - self.mapping = getattr(config, self.__class__.__name__) + self.mapping = getattr(config, config_key or self.__class__.__name__) if config else {} @classmethod def _extract_key(cls, value: str) -> typing.Union[str, bool]: diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index 80a426b..3ebd44f 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -51,6 +51,7 @@ def __init__(self, config, *args, **kwargs): 'width': Quantity('width', unit=units.pixel), 'height': Quantity('height', unit=units.pixel), 'scan_type': YesNo('interlaced', yes='Interlaced', no='Progressive', default='Progressive', + config=config, config_key='ScanType', description='video scan type'), 'resolution': None, # populated with ResolutionRule # 'bit_depth', Property('bit_depth', Integer('video bit depth')), diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index e431589..9bf08a0 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -255,7 +255,7 @@ def __init__(self, config, suggested_path): 'language': LanguageRule('audio language'), 'channels': AudioChannelsRule('audio channels'), '_atmosrule': AtmosRule('atmos rule'), - '_dtshdrule': DtsHdRule('dts-hd rule'), + '_dtshdrule': DtsHdRule(config, 'dts-hd rule'), }, 'subtitle': { 'language': LanguageRule('subtitle language'), diff --git a/knowit/rules/audio/dtshd.py b/knowit/rules/audio/dtshd.py index 53f47c8..b9f394f 100644 --- a/knowit/rules/audio/dtshd.py +++ b/knowit/rules/audio/dtshd.py @@ -1,3 +1,4 @@ +import typing from knowit.rule import Rule @@ -5,6 +6,11 @@ class DtsHdRule(Rule): """DTS-HD rule.""" + def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, **kwargs): + super().__init__(name, **kwargs) + self.audio_codecs = getattr(config, 'AudioCodec') + self.audio_profiles = getattr(config, 'AudioProfile') + @classmethod def _redefine(cls, props, name, index): actual = props.get(name) @@ -17,5 +23,8 @@ def _redefine(cls, props, name, index): def execute(self, props, pv_props, context): """Execute the rule against properties.""" - if props.get('codec') == 'DTS' and props.get('profile') in ('Master Audio', 'High Resolution Audio'): - props['codec'] = 'DTS-HD' + profile = context.get('profile') or 'default' + + if props.get('codec') == getattr(self.audio_codecs['DTS'], profile) and props.get('profile') in ( + getattr(self.audio_profiles['MA'], profile), getattr(self.audio_profiles['HRA'], profile)): + props['codec'] = getattr(self.audio_codecs['DTS-HD'], profile) diff --git a/tests/__init__.py b/tests/__init__.py index 3f935dc..6439727 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,6 +7,7 @@ from io import BytesIO from zipfile import ZipFile +import babelfish import requests import yaml from yaml.constructor import Constructor @@ -271,12 +272,17 @@ def is_iterable(obj): return isinstance(obj, (tuple, list)) +def to_string(profile: str, value): + formatted_value = format_property(profile, value) + return str(formatted_value) if formatted_value is not None else None + + def check_equals(expected, actual, different, options, prefix=''): if isinstance(expected, Mapping): check_mapping_equals(expected, actual, different=different, options=options, prefix=prefix) elif is_iterable(expected): check_sequence_equals(expected, actual, different=different, options=options, prefix=prefix) - elif format_property(options['profile'], expected) != format_property(options['profile'], actual): + elif to_string(options['profile'], expected) != to_string(options['profile'], actual): different.append((prefix, expected, actual)) diff --git a/tests/conftest.py b/tests/conftest.py index 908784f..73c5afa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ from knowit.providers import EnzymeProvider from knowit.providers.ffmpeg import FFmpegCliExecutor, FFmpegExecutor from knowit.providers.mediainfo import MediaInfoCTypesExecutor, MediaInfoCliExecutor, MediaInfoExecutor +from knowit.providers.mkvmerge import MkvMergeCliExecutor, MkvMergeExecutor @pytest.fixture @@ -23,7 +24,7 @@ def config(): @pytest.fixture def options(): - return {} + return {'profile': 'code'} def setup_mediainfo(executor, monkeypatch, options): @@ -66,6 +67,22 @@ def ffmpeg(monkeypatch, options): return data +@pytest.fixture +def mkvmerge(monkeypatch, options): + options['provider'] = 'mkvmerge' + api.available_providers.clear() + executor = MkvMergeCliExecutor.create() + get_executor = Mock() + get_executor.return_value = executor + monkeypatch.setattr(MkvMergeExecutor, 'get_executor_instance', get_executor) + + data = {} + extract_info = executor.extract_info + monkeypatch.setattr(executor, 'extract_info', + lambda filename: data[filename] if filename in data else extract_info(filename)) + return data + + @pytest.fixture def enzyme(monkeypatch, options): options['provider'] = 'enzyme' diff --git a/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml b/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml index 19a7f0e..e442701 100644 --- a/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml +++ b/tests/data/enzyme/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml @@ -4,12 +4,12 @@ duration: 0:01:37 container: mkv video: - id: 1 - language: Undetermined + language: und width: 1920 pixel height: 1080 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 1080p - codec: H.264 + codec: H264 default: true audio: - id: 2 diff --git a/tests/data/enzyme/test1.mkv.yml b/tests/data/enzyme/test1.mkv.yml index 6b9b834..5cead00 100644 --- a/tests/data/enzyme/test1.mkv.yml +++ b/tests/data/enzyme/test1.mkv.yml @@ -1,18 +1,18 @@ -duration: 0:01:27 +duration: '0:01:27.336000' path: tests/data/videos/test1.mkv container: mkv -size: 23.34 MB +size: 23339337 byte video: - id: 1 - language: Undetermined + language: und width: 854 pixel height: 480 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 480p default: true audio: - id: 2 - language: Undetermined + language: und codec: MP3 channels_count: 2 channels: '2.0' diff --git a/tests/data/enzyme/test2.mkv.yml b/tests/data/enzyme/test2.mkv.yml index 95d4d96..4daa1dc 100644 --- a/tests/data/enzyme/test2.mkv.yml +++ b/tests/data/enzyme/test2.mkv.yml @@ -1,19 +1,19 @@ -duration: 0:00:47 +duration: '0:00:47.509000' path: tests/data/videos/test2.mkv container: mkv -size: 21.14 MB +size: 21142764 byte video: - id: 1 - language: Undetermined + language: und width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 576p - codec: H.264 + codec: H264 default: true audio: - id: 2 - language: Undetermined + language: und codec: AAC channels_count: 2 channels: '2.0' diff --git a/tests/data/enzyme/test3.mkv.yml b/tests/data/enzyme/test3.mkv.yml index 9989256..c628c2c 100644 --- a/tests/data/enzyme/test3.mkv.yml +++ b/tests/data/enzyme/test3.mkv.yml @@ -1,15 +1,15 @@ -duration: 0:00:49 +duration: '0:00:49.064000' path: tests/data/videos/test3.mkv container: mkv -size: 21.06 MB +size: 21061472 byte video: - id: 1 - language: Undetermined + language: und width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 576p - codec: H.264 + codec: H264 default: true audio: - id: 2 diff --git a/tests/data/enzyme/test5.mkv.yml b/tests/data/enzyme/test5.mkv.yml index 7475063..7e042c8 100644 --- a/tests/data/enzyme/test5.mkv.yml +++ b/tests/data/enzyme/test5.mkv.yml @@ -1,19 +1,19 @@ -duration: 0:00:46 +duration: '0:00:46.665000' path: tests/data/videos/test5.mkv container: mkv -size: 31.76 MB +size: 31762747 byte video: - id: 1 - language: Undetermined + language: und width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 576p - codec: H.264 + codec: H264 default: true audio: - id: 2 - language: Undetermined + language: und codec: AAC channels_count: 2 channels: '2.0' @@ -27,18 +27,18 @@ subtitle: - id: 3 default: true - id: 4 - language: Hungarian + language: hu - id: 5 - language: German + language: de - id: 6 - language: French + language: fr - id: 8 - language: Spanish + language: es - id: 9 - language: Italian + language: it - id: 11 - language: Japanese + language: ja - id: 7 - language: Undetermined + language: und provider: name: enzyme \ No newline at end of file diff --git a/tests/data/enzyme/test6.mkv.yml b/tests/data/enzyme/test6.mkv.yml index 74c568c..8b97bde 100644 --- a/tests/data/enzyme/test6.mkv.yml +++ b/tests/data/enzyme/test6.mkv.yml @@ -1,17 +1,17 @@ -duration: 0:01:27 +duration: '0:01:27.336000' path: tests/data/videos/test6.mkv container: mkv -size: 23.34 MB +size: 23343928 byte video: - id: 1 - language: Undetermined + language: und width: 854 pixel height: 480 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 480p audio: - id: 2 - language: Undetermined + language: und codec: MP3 channels_count: 2 channels: '2.0' diff --git a/tests/data/enzyme/test7.mkv.yml b/tests/data/enzyme/test7.mkv.yml index 87b5b9c..7944bf3 100644 --- a/tests/data/enzyme/test7.mkv.yml +++ b/tests/data/enzyme/test7.mkv.yml @@ -1,18 +1,18 @@ -duration: 0:00:37 +duration: '0:00:37.043000' path: tests/data/videos/test7.mkv container: mkv -size: 21.85 MB +size: 21848518 byte video: - id: 1 - language: Undetermined + language: und width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 576p - codec: H.264 + codec: H264 audio: - id: 2 - language: Undetermined + language: und codec: AAC channels_count: 2 channels: '2.0' diff --git a/tests/data/enzyme/test8.mkv.yml b/tests/data/enzyme/test8.mkv.yml index d340ea8..f2291d3 100644 --- a/tests/data/enzyme/test8.mkv.yml +++ b/tests/data/enzyme/test8.mkv.yml @@ -1,18 +1,18 @@ -duration: 0:00:47 +duration: '0:00:47.341000' path: tests/data/videos/test8.mkv container: mkv -size: 21.22 MB +size: 21224737 byte video: - id: 1 - language: Undetermined + language: und width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE resolution: 576p - codec: H.264 + codec: H264 audio: - id: 2 - language: Undetermined + language: und codec: AAC channels_count: 2 channels: '2.0' diff --git a/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml b/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml index a8b28dc..106175b 100644 --- a/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml +++ b/tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml @@ -1,32 +1,32 @@ title: 7.1Ch DTS-HD MA - Speaker Mapping Test File path: tests/data/ffmpeg/7.1-dts-hd-ma-speaker-mapping-test-file.mkv -duration: 0:01:37 -size: 40.77 MB -bit_rate: 3.3 Mbps +duration: '0:01:37.931000' +size: 40772443 byte +bit_rate: 3330707 container: mkv video: - id: 0 width: 1920 pixel height: 1080 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 1080p frame_rate: 23.976 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN default: true audio: - id: 1 name: 7.1Ch DTS-HD MA - language: English - codec: DTS-HD - profile: Master Audio + language: en + codec: DTSHD + profile: MA channels_count: 8 channels: '7.1' bit_depth: 24 bit - sampling_rate: 48.0 KHz + sampling_rate: 48000 default: true provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/media_001.mkv.json b/tests/data/ffmpeg/media_001.mkv.json new file mode 100644 index 0000000..5517362 --- /dev/null +++ b/tests/data/ffmpeg/media_001.mkv.json @@ -0,0 +1,956 @@ +{ + "format": { + "bit_rate": "1231233", + "duration": "1:23:45.670000", + "filename": "videofile.mkv", + "format_long_name": "Matroska / WebM", + "format_name": "matroska,webm", + "nb_programs": 0, + "nb_streams": 24, + "probe_score": 100, + "size": "12345678901", + "start_time": "0:00:00.000000", + "tags": { + "creation_time": "2015-12-28T12:34:56.000000Z", + "encoder": "libebml v1.3.4 + libmatroska v1.4.5", + "title": "Super Title" + } + }, + "streams": [ + { + "avg_frame_rate": "24000/1001", + "bits_per_raw_sample": "8", + "chroma_location": "left", + "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", + "codec_name": "h264", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1001/48000", + "codec_type": "video", + "coded_height": 1080, + "coded_width": 1920, + "color_primaries": "bt709", + "color_range": "tv", + "color_space": "bt709", + "color_transfer": "bt709", + "display_aspect_ratio": "16:9", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 1, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "field_order": "progressive", + "has_b_frames": 1, + "height": 1080, + "index": 0, + "is_avc": "true", + "level": 41, + "nal_length_size": "4", + "pix_fmt": "yuv420p", + "profile": "High", + "r_frame_rate": "24000/1001", + "refs": 1, + "sample_aspect_ratio": "1:1", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "title": "Super Title" + }, + "time_base": "1/1000", + "width": 1920 + }, + { + "avg_frame_rate": "0/0", + "bits_per_raw_sample": "24", + "bits_per_sample": 0, + "channel_layout": "7.1", + "channels": 8, + "codec_long_name": "DCA (DTS Coherent Acoustics)", + "codec_name": "dts", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1/48000", + "codec_type": "audio", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 1, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "index": 1, + "profile": "DTS-HD MA", + "r_frame_rate": "0/0", + "sample_fmt": "s32p", + "sample_rate": "48000", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "eng", + "title": "Super Title" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "bit_rate": "1536000", + "bits_per_sample": 0, + "channel_layout": "5.1(side)", + "channels": 6, + "codec_long_name": "DCA (DTS Coherent Acoustics)", + "codec_name": "dts", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1/48000", + "codec_type": "audio", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "index": 2, + "profile": "DTS", + "r_frame_rate": "0/0", + "sample_fmt": "fltp", + "sample_rate": "48000", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "eng", + "title": "Super Title" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "bit_rate": "320000", + "bits_per_sample": 0, + "channel_layout": "stereo", + "channels": 2, + "codec_long_name": "ATSC A/52A (AC-3)", + "codec_name": "ac3", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1/48000", + "codec_type": "audio", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "dmix_mode": "-1", + "index": 3, + "loro_cmixlev": "-1.000000", + "loro_surmixlev": "-1.000000", + "ltrt_cmixlev": "-1.000000", + "ltrt_surmixlev": "-1.000000", + "r_frame_rate": "0/0", + "sample_fmt": "fltp", + "sample_rate": "48000", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "eng", + "title": "Super Title" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "bits_per_sample": 0, + "channel_layout": "5.1(side)", + "channels": 6, + "codec_long_name": "DCA (DTS Coherent Acoustics)", + "codec_name": "dts", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1/48000", + "codec_type": "audio", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "index": 4, + "profile": "DTS-HD HRA", + "r_frame_rate": "0/0", + "sample_fmt": "fltp", + "sample_rate": "48000", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "fre", + "title": "Super Title" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "bit_rate": "1536000", + "bits_per_sample": 0, + "channel_layout": "5.1(side)", + "channels": 6, + "codec_long_name": "DCA (DTS Coherent Acoustics)", + "codec_name": "dts", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1/48000", + "codec_type": "audio", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "index": 5, + "profile": "DTS", + "r_frame_rate": "0/0", + "sample_fmt": "fltp", + "sample_rate": "48000", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "fre", + "title": "Super Title" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "bit_rate": "640000", + "bits_per_sample": 0, + "channel_layout": "5.1(side)", + "channels": 6, + "codec_long_name": "ATSC A/52A (AC-3)", + "codec_name": "ac3", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1/48000", + "codec_type": "audio", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "dmix_mode": "-1", + "index": 6, + "loro_cmixlev": "-1.000000", + "loro_surmixlev": "-1.000000", + "ltrt_cmixlev": "-1.000000", + "ltrt_surmixlev": "-1.000000", + "r_frame_rate": "0/0", + "sample_fmt": "fltp", + "sample_rate": "48000", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "cze", + "title": "Super Title" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "bit_rate": "640000", + "bits_per_sample": 0, + "channel_layout": "5.1(side)", + "channels": 6, + "codec_long_name": "ATSC A/52A (AC-3)", + "codec_name": "ac3", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "1/48000", + "codec_type": "audio", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "dmix_mode": "-1", + "index": 7, + "loro_cmixlev": "-1.000000", + "loro_surmixlev": "-1.000000", + "ltrt_cmixlev": "-1.000000", + "ltrt_surmixlev": "-1.000000", + "r_frame_rate": "0/0", + "sample_fmt": "fltp", + "sample_rate": "48000", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "hin", + "title": "Super Title" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 8, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "eng", + "title": "English-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 9, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "fre", + "title": "French-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 10, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "cze", + "title": "Czech-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 11, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "dut", + "title": "Dutch-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 12, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "ara", + "title": "Arabic-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 13, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "dan", + "title": "Danish-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 14, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "fin", + "title": "Finnish-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 15, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "nor", + "title": "Norwegian-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 16, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "swe", + "title": "Swedish-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 17, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "fre", + "title": "French-FORCED-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 18, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "cze", + "title": "Czech-FORCED-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "codec_long_name": "HDMV Presentation Graphic Stream subtitles", + "codec_name": "hdmv_pgs_subtitle", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "subtitle", + "disposition": { + "attached_pic": 0, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 503170, + "index": 19, + "r_frame_rate": "0/0", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "language": "hin", + "title": "Hindi-FORCED-PGS" + }, + "time_base": "1/1000" + }, + { + "avg_frame_rate": "0/0", + "bits_per_raw_sample": "8", + "chroma_location": "center", + "codec_long_name": "Motion JPEG", + "codec_name": "mjpeg", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "video", + "coded_height": 600, + "coded_width": 1067, + "color_range": "pc", + "color_space": "bt470bg", + "display_aspect_ratio": "0:1", + "disposition": { + "attached_pic": 1, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 745804800, + "has_b_frames": 0, + "height": 600, + "index": 20, + "level": -99, + "pix_fmt": "yuvj444p", + "r_frame_rate": "90000/1", + "refs": 1, + "sample_aspect_ratio": "0:1", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "filename": "cover_land.jpg", + "mimetype": "image/jpeg" + }, + "time_base": "1/90000", + "width": 1067 + }, + { + "avg_frame_rate": "0/0", + "bits_per_raw_sample": "8", + "chroma_location": "center", + "codec_long_name": "Motion JPEG", + "codec_name": "mjpeg", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "video", + "coded_height": 176, + "coded_width": 120, + "color_range": "pc", + "color_space": "bt470bg", + "display_aspect_ratio": "0:1", + "disposition": { + "attached_pic": 1, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 745804800, + "has_b_frames": 0, + "height": 176, + "index": 21, + "level": -99, + "pix_fmt": "yuvj444p", + "r_frame_rate": "90000/1", + "refs": 1, + "sample_aspect_ratio": "0:1", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "filename": "small_cover.jpg", + "mimetype": "image/jpeg" + }, + "time_base": "1/90000", + "width": 120 + }, + { + "avg_frame_rate": "0/0", + "bits_per_raw_sample": "8", + "chroma_location": "center", + "codec_long_name": "Motion JPEG", + "codec_name": "mjpeg", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "video", + "coded_height": 120, + "coded_width": 213, + "color_range": "pc", + "color_space": "bt470bg", + "display_aspect_ratio": "0:1", + "disposition": { + "attached_pic": 1, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 745804800, + "has_b_frames": 0, + "height": 120, + "index": 22, + "level": -99, + "pix_fmt": "yuvj444p", + "r_frame_rate": "90000/1", + "refs": 1, + "sample_aspect_ratio": "0:1", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "filename": "small_cover_land.jpg", + "mimetype": "image/jpeg" + }, + "time_base": "1/90000", + "width": 213 + }, + { + "avg_frame_rate": "0/0", + "bits_per_raw_sample": "8", + "chroma_location": "center", + "codec_long_name": "Motion JPEG", + "codec_name": "mjpeg", + "codec_tag": "0x0000", + "codec_tag_string": "[0][0][0][0]", + "codec_time_base": "0/1", + "codec_type": "video", + "coded_height": 882, + "coded_width": 600, + "color_range": "pc", + "color_space": "bt470bg", + "display_aspect_ratio": "0:1", + "disposition": { + "attached_pic": 1, + "clean_effects": 0, + "comment": 0, + "default": 0, + "dub": 0, + "forced": 0, + "hearing_impaired": 0, + "karaoke": 0, + "lyrics": 0, + "original": 0, + "timed_thumbnails": 0, + "visual_impaired": 0 + }, + "duration": "1:23:45.670000", + "duration_ts": 745804800, + "has_b_frames": 0, + "height": 882, + "index": 23, + "level": -99, + "pix_fmt": "yuvj444p", + "r_frame_rate": "90000/1", + "refs": 1, + "sample_aspect_ratio": "0:1", + "start_pts": 0, + "start_time": "0:00:00.000000", + "tags": { + "filename": "cover.jpg", + "mimetype": "image/jpeg" + }, + "time_base": "1/90000", + "width": 600 + } + ] +} \ No newline at end of file diff --git a/tests/data/ffmpeg/media_001.mkv.yml b/tests/data/ffmpeg/media_001.mkv.yml new file mode 100644 index 0000000..e2ca2a2 --- /dev/null +++ b/tests/data/ffmpeg/media_001.mkv.yml @@ -0,0 +1,131 @@ +title: Super Title +path: videofile.mkv +container: mkv +duration: '1:23:45.670000' +size: 12345678901 byte +bit_rate: 1231233 bps +video: +- id: 0 + name: Super Title + width: 1920 pixel + height: 1080 pixel + scan_type: PROGRESSIVE + aspect_ratio: 1.778 + pixel_aspect_ratio: 1.0 + resolution: 1080p + frame_rate: 23.976 FPS + bit_depth: 8 bit + codec: H264 + profile: HIGH + default: true +audio: +- id: 1 + name: Super Title + language: en + codec: DTSHD + profile: MA + channels_count: 8 + channels: '7.1' + bit_depth: 24 bit + sampling_rate: 48000 Hz + default: true +- id: 2 + name: Super Title + language: en + codec: DTS + channels_count: 6 + channels: '5.1' + bit_rate: 1536000 bps + sampling_rate: 48000 Hz +- id: 3 + name: Super Title + language: en + codec: AC3 + channels_count: 2 + channels: '2.0' + bit_rate: 320000 bps + sampling_rate: 48000 Hz +- id: 4 + name: Super Title + language: fr + codec: DTSHD + profile: HRA + channels_count: 6 + channels: '5.1' + sampling_rate: 48000 Hz +- id: 5 + name: Super Title + language: fr + codec: DTS + channels_count: 6 + channels: '5.1' + bit_rate: 1536000 bps + sampling_rate: 48000 Hz +- id: 6 + name: Super Title + language: cs + codec: AC3 + channels_count: 6 + channels: '5.1' + bit_rate: 640000 bps + sampling_rate: 48000 Hz +- id: 7 + name: Super Title + language: hi + codec: AC3 + channels_count: 6 + channels: '5.1' + bit_rate: 640000 bps + sampling_rate: 48000 Hz +subtitle: +- id: 8 + name: English-PGS + language: en + format: PGS +- id: 9 + name: French-PGS + language: fr + format: PGS +- id: 10 + name: Czech-PGS + language: cs + format: PGS +- id: 11 + name: Dutch-PGS + language: nl + format: PGS +- id: 12 + name: Arabic-PGS + language: ar + format: PGS +- id: 13 + name: Danish-PGS + language: da + format: PGS +- id: 14 + name: Finnish-PGS + language: fi + format: PGS +- id: 15 + name: Norwegian-PGS + language: "no" + format: PGS +- id: 16 + name: Swedish-PGS + language: sv + format: PGS +- id: 17 + name: French-FORCED-PGS + language: fr + format: PGS +- id: 18 + name: Czech-FORCED-PGS + language: cs + format: PGS +- id: 19 + name: Hindi-FORCED-PGS + language: hi + format: PGS +provider: + name: ffmpeg + diff --git a/tests/data/ffmpeg/test1.mkv.yml b/tests/data/ffmpeg/test1.mkv.yml index c3fa55a..791b45d 100644 --- a/tests/data/ffmpeg/test1.mkv.yml +++ b/tests/data/ffmpeg/test1.mkv.yml @@ -1,27 +1,27 @@ title: Big Buck Bunny - test 1 path: tests/data/videos/test1.mkv -duration: 0:01:27 -size: 23.34 MB -bit_rate: 2.1 Mbps +duration: '0:01:27.336000' +size: 23339337 byte +bit_rate: 2137889 container: mkv video: - id: 0 width: 854 pixel height: 480 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.779 pixel_aspect_ratio: 1.0 resolution: 480p frame_rate: 24.0 FPS - codec: Microsoft MPEG-4 v2 + codec: MSMPEG4V2 default: true audio: - id: 1 codec: MP3 channels_count: 2 channels: '2.0' - bit_rate: 208.0 Kbps - sampling_rate: 48.0 KHz + bit_rate: 208000 + sampling_rate: 48000 default: true provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test2.mkv.yml b/tests/data/ffmpeg/test2.mkv.yml index 5e65620..b84fc0b 100644 --- a/tests/data/ffmpeg/test2.mkv.yml +++ b/tests/data/ffmpeg/test2.mkv.yml @@ -1,29 +1,29 @@ title: Elephant Dream - test 2 path: tests/data/videos/test2.mkv -duration: 0:00:47 -size: 21.14 MB -bit_rate: 3.6 Mbps +duration: '0:00:47.509000' +size: 21142764 byte +bit_rate: 3560212 container: mkv video: - id: 0 width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 2.351 pixel_aspect_ratio: 1.322 resolution: 1080p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN default: true audio: - id: 1 codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz + sampling_rate: 48000 default: true provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test3.mkv.yml b/tests/data/ffmpeg/test3.mkv.yml index c88aa4d..0dcce02 100644 --- a/tests/data/ffmpeg/test3.mkv.yml +++ b/tests/data/ffmpeg/test3.mkv.yml @@ -1,30 +1,30 @@ title: Elephant Dream - test 3 path: tests/data/videos/test3.mkv -duration: 0:00:49 -size: 21.06 MB -bit_rate: 3.4 Mbps +duration: '0:00:49.064000' +size: 21061472 byte +bit_rate: 3434122 container: mkv video: - id: 0 width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN default: true audio: - id: 1 - language: English + language: en codec: MP3 channels_count: 2 channels: '2.0' - bit_rate: 172.0 Kbps - sampling_rate: 48.0 KHz + bit_rate: 172001 + sampling_rate: 48000 default: true provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test4.mkv.yml b/tests/data/ffmpeg/test4.mkv.yml index b1be1e3..8c26f69 100644 --- a/tests/data/ffmpeg/test4.mkv.yml +++ b/tests/data/ffmpeg/test4.mkv.yml @@ -1,11 +1,11 @@ path: tests/data/videos/test4.mkv -size: 21.31 MB +size: 21313902 byte container: mkv video: - id: 0 width: 1280 pixel height: 720 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 720p @@ -13,10 +13,10 @@ video: default: true audio: - id: 1 - codec: Vorbis + codec: VORBIS channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz + sampling_rate: 48000 default: true provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml b/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml index 3ce683e..064f0b0 100644 --- a/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml +++ b/tests/data/ffmpeg/test5-ffmpeg-v2.8.15.mkv.yml @@ -1,62 +1,62 @@ title: Big Buck Bunny - test 8 path: test5.mkv -duration: 0:00:46 -size: 31.76 MB -bit_rate: 5.4 Mbps +duration: '0:00:46.665000' +size: 31762747 byte +bit_rate: 5445236 container: mkv video: - id: 0 width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN default: true audio: - id: 1 codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz + sampling_rate: 48000 default: true - id: 8 name: Commentary - language: English + language: en codec: AAC - profile: Low Complexity + profile: LC channels_count: 1 channels: '1.0' - sampling_rate: 22.1 KHz + sampling_rate: 22050 subtitle: - id: 2 - language: English - format: SubRip + language: en + format: SUBRIP default: true - id: 3 - language: Hungarian - format: SubRip + language: hu + format: SUBRIP - id: 4 - language: German - format: SubRip + language: de + format: SUBRIP - id: 5 - language: French - format: SubRip + language: fr + format: SUBRIP - id: 6 - language: Spanish - format: SubRip + language: es + format: SUBRIP - id: 7 - language: Italian - format: SubRip + language: it + format: SUBRIP - id: 9 - language: Japanese - format: SubRip + language: ja + format: SUBRIP - id: 10 - format: SubRip + format: SUBRIP provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test5.mkv.yml b/tests/data/ffmpeg/test5.mkv.yml index ba8cbde..6dfad5e 100644 --- a/tests/data/ffmpeg/test5.mkv.yml +++ b/tests/data/ffmpeg/test5.mkv.yml @@ -1,62 +1,62 @@ title: Big Buck Bunny - test 8 path: tests/data/videos/test5.mkv -duration: 0:00:46 -size: 31.76 MB -bit_rate: 5.4 Mbps +duration: '0:00:46.665000' +size: 31762747 byte +bit_rate: 5445236 container: mkv video: - id: 0 width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN default: true audio: - id: 1 codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz + sampling_rate: 48000 default: true - id: 8 name: Commentary - language: English + language: en codec: AAC - profile: Low Complexity + profile: LC channels_count: 1 channels: '1.0' - sampling_rate: 22.1 KHz + sampling_rate: 22050 subtitle: - id: 2 - language: English - format: SubRip + language: en + format: SUBRIP default: true - id: 3 - language: Hungarian - format: SubRip + language: hu + format: SUBRIP - id: 4 - language: German - format: SubRip + language: de + format: SUBRIP - id: 5 - language: French - format: SubRip + language: fr + format: SUBRIP - id: 6 - language: Spanish - format: SubRip + language: es + format: SUBRIP - id: 7 - language: Italian - format: SubRip + language: it + format: SUBRIP - id: 9 - language: Japanese - format: SubRip + language: ja + format: SUBRIP - id: 10 - format: SubRip + format: SUBRIP provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test6.mkv.yml b/tests/data/ffmpeg/test6.mkv.yml index 9bc505d..0bde70b 100644 --- a/tests/data/ffmpeg/test6.mkv.yml +++ b/tests/data/ffmpeg/test6.mkv.yml @@ -1,25 +1,25 @@ title: Big Buck Bunny - test 6 path: tests/data/videos/test6.mkv -duration: 0:01:27 -size: 23.34 MB -bit_rate: 2.1 Mbps +duration: '0:01:27.336000' +size: 23343928 byte +bit_rate: 2138309 container: mkv video: - id: 0 width: 854 pixel height: 480 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.779 pixel_aspect_ratio: 1.0 resolution: 480p frame_rate: 24.0 FPS - codec: Microsoft MPEG-4 v2 + codec: MSMPEG4V2 audio: - id: 1 codec: MP3 channels_count: 2 channels: '2.0' - bit_rate: 208.0 Kbps - sampling_rate: 48.0 KHz + bit_rate: 208000 + sampling_rate: 48000 provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test7.mkv.yml b/tests/data/ffmpeg/test7.mkv.yml index 9ef9b0d..9ecde55 100644 --- a/tests/data/ffmpeg/test7.mkv.yml +++ b/tests/data/ffmpeg/test7.mkv.yml @@ -1,27 +1,27 @@ title: Big Buck Bunny - test 7 path: tests/data/videos/test7.mkv -duration: 0:00:37 -size: 21.85 MB -bit_rate: 4.7 Mbps +duration: '0:00:37.043000' +size: 21848518 byte +bit_rate: 4718520 container: mkv video: - id: 0 width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN audio: - id: 1 codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz + sampling_rate: 48000 provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/ffmpeg/test8.mkv.yml b/tests/data/ffmpeg/test8.mkv.yml index 2d1bd7c..1883eeb 100644 --- a/tests/data/ffmpeg/test8.mkv.yml +++ b/tests/data/ffmpeg/test8.mkv.yml @@ -1,27 +1,27 @@ title: Big Buck Bunny - test 8 path: tests/data/videos/test8.mkv -duration: 0:00:47 -size: 21.22 MB -bit_rate: 3.6 Mbps +duration: '0:00:47.341000' +size: 21224737 byte +bit_rate: 3586698 container: mkv video: - id: 0 width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN audio: - id: 1 codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz + sampling_rate: 48000 provider: name: ffmpeg \ No newline at end of file diff --git a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml index b265188..951ae1c 100644 --- a/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml +++ b/tests/data/mediainfo/7.1-dts-hd-ma-speaker-mapping-test-file.mkv.yml @@ -1,38 +1,38 @@ title: 7.1Ch DTS-HD MA - Speaker Mapping Test File path: tests/data/7.1-dts-hd-ma-speaker-mapping-test-file.mkv -duration: 0:01:37 -size: 40.77 MB -bit_rate: 3.3 Mbps +duration: '0:01:37.931000' +size: 40772443 byte +bit_rate: 3330708 container: mkv video: - id: 1 - duration: 0:01:37 + duration: '0:01:37.931000' width: 1920 pixel height: 1080 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 1080p frame_rate: 23.976 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN profile_level: '4' media_type: video/H264 default: true audio: - id: 2 name: 7.1Ch DTS-HD MA - language: English - duration: 0:01:37 - codec: DTS-HD - profile: Master Audio + language: en + duration: '0:01:37.931000' + codec: DTSHD + profile: MA channels_count: 8 channels: '7.1' bit_depth: 24 bit - bit_rate_mode: Variable - sampling_rate: 48.0 KHz - compression: Lossless + bit_rate_mode: VBR + sampling_rate: 48000 + compression: LOSSLESS default: true provider: name: mediainfo \ No newline at end of file diff --git a/tests/data/mediainfo/test1.mkv.yml b/tests/data/mediainfo/test1.mkv.yml index 158b5c4..0904725 100644 --- a/tests/data/mediainfo/test1.mkv.yml +++ b/tests/data/mediainfo/test1.mkv.yml @@ -1,32 +1,32 @@ title: Big Buck Bunny - test 1 path: tests/data/videos/test1.mkv -duration: 0:01:27 -size: 23.34 MB -bit_rate: 2.1 Mbps +duration: '0:01:27.336000' +size: 23339337 byte +bit_rate: 2137889 container: mkv video: - id: 1 - duration: 0:01:27 + duration: '0:01:27.333000' width: 854 pixel height: 480 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.779 pixel_aspect_ratio: 1.0 resolution: 480p frame_rate: 24.0 FPS - codec: Microsoft MPEG-4 v2 + codec: MSMPEG4V2 media_type: video/MP4V-ES default: true audio: - id: 2 - duration: 0:01:27 + duration: '0:01:27.336000' codec: MP3 - profile: Layer 3 + profile: LAYER3 channels_count: 2 channels: '2.0' - bit_rate_mode: Variable - sampling_rate: 48.0 KHz - compression: Lossy + bit_rate_mode: VBR + sampling_rate: 48000 + compression: LOSSY default: true provider: name: mediainfo \ No newline at end of file diff --git a/tests/data/mediainfo/test2.mkv.yml b/tests/data/mediainfo/test2.mkv.yml index 4b8eb30..6bb3d80 100644 --- a/tests/data/mediainfo/test2.mkv.yml +++ b/tests/data/mediainfo/test2.mkv.yml @@ -1,34 +1,34 @@ title: Elephant Dream - test 2 path: tests/data/videos/test2.mkv -duration: 0:00:47 -size: 21.14 MB -bit_rate: 3.6 Mbps +duration: '0:00:47.509000' +size: 21142764 byte +bit_rate: 3560212 container: mkv video: - id: 1 - duration: 0:00:47 + duration: '0:00:47.500000' width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 2.351 pixel_aspect_ratio: 1.322 resolution: 1080p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN profile_level: '3.1' media_type: video/H264 default: true audio: - id: 2 - duration: 0:00:47 + duration: '0:00:47.509000' codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz - compression: Lossy + sampling_rate: 48000 + compression: LOSSY default: true provider: name: mediainfo \ No newline at end of file diff --git a/tests/data/mediainfo/test3.mkv.yml b/tests/data/mediainfo/test3.mkv.yml index c097dc3..a63b346 100644 --- a/tests/data/mediainfo/test3.mkv.yml +++ b/tests/data/mediainfo/test3.mkv.yml @@ -1,36 +1,36 @@ title: Elephant Dream - test 3 path: tests/data/videos/test3.mkv -duration: 0:00:49 -size: 21.06 MB -bit_rate: 3.4 Mbps +duration: '0:00:49.064000' +size: 21061472 byte +bit_rate: 3434122 container: mkv video: - id: 1 - duration: 0:00:49 + duration: '0:00:49.083000' width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN profile_level: '3.1' media_type: video/H264 default: true audio: - id: 2 - language: English - duration: 0:00:49 + language: en + duration: '0:00:49.064000' codec: MP3 - profile: Layer 3 + profile: LAYER3 channels_count: 2 channels: '2.0' - bit_rate_mode: Variable - sampling_rate: 48.0 KHz - compression: Lossy + bit_rate_mode: VBR + sampling_rate: 48000 + compression: LOSSY default: true provider: name: mediainfo \ No newline at end of file diff --git a/tests/data/mediainfo/test4.mkv.yml b/tests/data/mediainfo/test4.mkv.yml index b279fee..d433e6b 100644 --- a/tests/data/mediainfo/test4.mkv.yml +++ b/tests/data/mediainfo/test4.mkv.yml @@ -1,26 +1,26 @@ path: tests/data/videos/test4.mkv -size: 21.31 MB +size: 21313902 byte container: mkv video: - id: 1 width: 1280 pixel height: 720 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 720p frame_rate: 24.0 FPS - bit_rate: 2.5 Mbps + bit_rate: 2500000 default: true audio: - id: 2 - codec: Vorbis + codec: VORBIS channels_count: 2 channels: '2.0' - bit_rate: 192.0 Kbps - bit_rate_mode: Variable - sampling_rate: 48.0 KHz - compression: Lossy + bit_rate: 192000 + bit_rate_mode: VBR + sampling_rate: 48000 + compression: LOSSY default: true provider: name: mediainfo diff --git a/tests/data/mediainfo/test5.mkv.yml b/tests/data/mediainfo/test5.mkv.yml index 8f4eea4..3e372c7 100644 --- a/tests/data/mediainfo/test5.mkv.yml +++ b/tests/data/mediainfo/test5.mkv.yml @@ -1,69 +1,69 @@ title: Big Buck Bunny - test 8 path: tests/data/videos/test5.mkv -duration: 0:00:46 -size: 31.76 MB -bit_rate: 5.4 Mbps +duration: '0:00:46.665000' +size: 31762747 byte +bit_rate: 5445237 container: mkv video: - id: 1 - duration: 0:00:46 + duration: '0:00:46.667000' width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN profile_level: '3.1' media_type: video/H264 default: true audio: - id: 2 - duration: 0:00:46 + duration: '0:00:46.665000' codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz - compression: Lossy + sampling_rate: 48000 + compression: LOSSY default: true - id: 10 name: Commentary - language: English - duration: 0:00:46 + language: en + duration: '0:00:46.665000' codec: AAC - profile: Low Complexity + profile: LC channels_count: 1 channels: '1.0' - sampling_rate: 22.1 KHz - compression: Lossy + sampling_rate: 22050 + compression: LOSSY subtitle: - id: 3 - language: English - format: SubRip + language: en + format: SUBRIP default: true - id: 4 - language: Hungarian - format: SubRip + language: hu + format: SUBRIP - id: 5 - language: German - format: SubRip + language: de + format: SUBRIP - id: 6 - language: French - format: SubRip + language: fr + format: SUBRIP - id: 8 - language: Spanish - format: SubRip + language: es + format: SUBRIP - id: 9 - language: Italian - format: SubRip + language: it + format: SUBRIP - id: 11 - language: Japanese - format: SubRip + language: ja + format: SUBRIP - id: 7 - format: SubRip + format: SUBRIP provider: name: mediainfo \ No newline at end of file diff --git a/tests/data/mediainfo/test6.mkv.yml b/tests/data/mediainfo/test6.mkv.yml index 251dae4..56fa859 100644 --- a/tests/data/mediainfo/test6.mkv.yml +++ b/tests/data/mediainfo/test6.mkv.yml @@ -1,30 +1,30 @@ title: Big Buck Bunny - test 6 path: tests/data/videos/test6.mkv -duration: 0:01:27 -size: 23.34 MB -bit_rate: 2.1 Mbps +duration: '0:01:27.336000' +size: 23343928 byte +bit_rate: 2138310 container: mkv video: - id: 1 - duration: 0:01:27 + duration: '0:01:27.333000' width: 854 pixel height: 480 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.779 pixel_aspect_ratio: 1.0 resolution: 480p frame_rate: 24.0 FPS - codec: Microsoft MPEG-4 v2 + codec: MSMPEG4V2 media_type: video/MP4V-ES audio: - id: 2 - duration: 0:01:27 + duration: '0:01:27.336000' codec: MP3 - profile: Layer 3 + profile: LAYER3 channels_count: 2 channels: '2.0' - bit_rate_mode: Variable - sampling_rate: 48.0 KHz - compression: Lossy + bit_rate_mode: VBR + sampling_rate: 48000 + compression: LOSSY provider: name: mediainfo \ No newline at end of file diff --git a/tests/data/mediainfo/test7.mkv.yml b/tests/data/mediainfo/test7.mkv.yml index 80dfb40..e845695 100644 --- a/tests/data/mediainfo/test7.mkv.yml +++ b/tests/data/mediainfo/test7.mkv.yml @@ -1,32 +1,32 @@ title: Big Buck Bunny - test 7 path: tests/data/videos/test7.mkv -duration: 0:00:37 -size: 21.85 MB -bit_rate: 4.7 Mbps +duration: '0:00:37.043000' +size: 21848518 byte +bit_rate: 4718520 container: mkv video: - id: 1 - duration: 0:00:37 + duration: '0:00:37.042000' width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN profile_level: '3.1' media_type: video/H264 audio: - id: 2 - duration: 0:00:37 + duration: '0:00:37.043000' codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz - compression: Lossy + sampling_rate: 48000 + compression: LOSSY provider: name: mediainfo \ No newline at end of file diff --git a/tests/data/mediainfo/test8.mkv.yml b/tests/data/mediainfo/test8.mkv.yml index cc307fb..1a810c5 100644 --- a/tests/data/mediainfo/test8.mkv.yml +++ b/tests/data/mediainfo/test8.mkv.yml @@ -1,32 +1,32 @@ title: Big Buck Bunny - test 8 path: tests/data/videos/test8.mkv -duration: 0:00:47 -size: 21.22 MB -bit_rate: 3.6 Mbps +duration: '0:00:47.341000' +size: 21224737 byte +bit_rate: 3586699 container: mkv video: - id: 1 - duration: 0:00:47 + duration: '0:00:47.333000' width: 1024 pixel height: 576 pixel - scan_type: Progressive + scan_type: PROGRESSIVE aspect_ratio: 1.778 pixel_aspect_ratio: 1.0 resolution: 576p frame_rate: 24.0 FPS bit_depth: 8 bit - codec: H.264 - profile: Main + codec: H264 + profile: MAIN profile_level: '3.1' media_type: video/H264 audio: - id: 2 - duration: 0:00:47 + duration: '0:00:47.341000' codec: AAC - profile: Low Complexity + profile: LC channels_count: 2 channels: '2.0' - sampling_rate: 48.0 KHz - compression: Lossy + sampling_rate: 48000 + compression: LOSSY provider: name: mediainfo \ No newline at end of file diff --git a/tests/test_enzyme/test_001.yml b/tests/test_enzyme/test_001.yml deleted file mode 100644 index 407a921..0000000 --- a/tests/test_enzyme/test_001.yml +++ /dev/null @@ -1,130 +0,0 @@ -input: - info: - duration: '1:23:45.678000' - title: Super Title - video_tracks: - - forced: false - display_height: 800 - name: Video Track 1 - language: null - default: true - aspect_ratio_type: null - enabled: true - number: 1 - crop: {} - height: 800 - width: 1920 - codec_name: null - codec_id: V_MPEG4/ISO/AVC - stereo_mode: null - lacing: false - display_unit: null - type: 1 - display_width: 1920 - interlaced: false - tags: [] - audio_tracks: - - bit_depth: null - forced: false - name: Audio Track 1 - language: null - default: true - output_sampling_frequency: null - enabled: true - number: 2 - sampling_frequency: 48000.0 - channels: 8 - codec_name: null - codec_id: A_DTS - lacing: true - type: 2 - recurse_seek_head: false - subtitle_tracks: - - forced: false - name: English SRT - language: null - default: true - enabled: true - number: 3 - codec_name: null - codec_id: S_TEXT/UTF8 - lacing: false - type: 17 - - forced: false - name: English SRT SDH - language: null - default: false - enabled: true - number: 4 - codec_name: null - codec_id: S_TEXT/UTF8 - lacing: false - type: 17 - - forced: false - name: English PGS - language: null - default: false - enabled: true - number: 5 - codec_name: null - codec_id: S_HDMV/PGS - lacing: false - type: 17 - - forced: false - name: French PGS - language: fre - default: false - enabled: true - number: 6 - codec_name: null - codec_id: S_HDMV/PGS - lacing: false - type: 17 - - forced: false - name: Spanish PGS - language: spa - default: false - enabled: true - number: 7 - codec_name: null - codec_id: S_HDMV/PGS - lacing: false - type: 17 - -expected: - title: Super Title - duration: '1:23:45.678000' - video: - - id: 1 - name: Video Track 1 - width: 1920 pixel - height: 800 pixel - scan_type: Progressive - resolution: 1080p - codec: H.264 - default: true - audio: - - id: 2 - name: Audio Track 1 - codec: DTS - channels_count: 8 - channels: '7.1' - default: true - subtitle: - - id: 3 - name: English SRT - language: en - default: true - - id: 4 - name: English SRT SDH - language: en - hearing_impaired: true - - id: 5 - name: English PGS - language: en - - id: 6 - name: French PGS - language: fr - - id: 7 - name: Spanish PGS - language: es diff --git a/tests/test_enzyme/test_002.yml b/tests/test_enzyme/test_002.yml deleted file mode 100644 index 2fe0ac7..0000000 --- a/tests/test_enzyme/test_002.yml +++ /dev/null @@ -1,13 +0,0 @@ -input: - subtitle_tracks: - - forced: false - name: English - enabled: true - number: 3 - codec_id: S_TEXT/UTF8 - -expected: - subtitle: - - id: 3 - name: English - language: en diff --git a/tests/test_ffmpeg/test_001.yml b/tests/test_ffmpeg/test_001.yml deleted file mode 100644 index dd0ca2a..0000000 --- a/tests/test_ffmpeg/test_001.yml +++ /dev/null @@ -1,986 +0,0 @@ -input: - format: - bit_rate: '1231233' - duration: '1:23:45.670000' - filename: videofile.mkv - format_long_name: Matroska / WebM - format_name: matroska,webm - nb_programs: 0 - nb_streams: 24 - probe_score: 100 - size: '12345678901' - start_time: '0:00:00.000000' - tags: - creation_time: '2015-12-28T12:34:56.000000Z' - encoder: libebml v1.3.4 + libmatroska v1.4.5 - title: Super Title - streams: - - avg_frame_rate: 24000/1001 - bits_per_raw_sample: '8' - chroma_location: left - codec_long_name: H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 - codec_name: h264 - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1001/48000 - codec_type: video - coded_height: 1080 - coded_width: 1920 - color_primaries: bt709 - color_range: tv - color_space: bt709 - color_transfer: bt709 - display_aspect_ratio: '16:9' - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 1 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - field_order: progressive - has_b_frames: 1 - height: 1080 - index: 0 - is_avc: 'true' - level: 41 - nal_length_size: '4' - pix_fmt: yuv420p - profile: High - r_frame_rate: 24000/1001 - refs: 1 - sample_aspect_ratio: '1:1' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - title: Super Title - time_base: 1/1000 - width: 1920 - - avg_frame_rate: 0/0 - bits_per_raw_sample: '24' - bits_per_sample: 0 - channel_layout: '7.1' - channels: 8 - codec_long_name: DCA (DTS Coherent Acoustics) - codec_name: dts - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1/48000 - codec_type: audio - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 1 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - index: 1 - profile: DTS-HD MA - r_frame_rate: 0/0 - sample_fmt: s32p - sample_rate: '48000' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: eng - title: Super Title - time_base: 1/1000 - - avg_frame_rate: 0/0 - bit_rate: '1536000' - bits_per_sample: 0 - channel_layout: 5.1(side) - channels: 6 - codec_long_name: DCA (DTS Coherent Acoustics) - codec_name: dts - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1/48000 - codec_type: audio - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - index: 2 - profile: DTS - r_frame_rate: 0/0 - sample_fmt: fltp - sample_rate: '48000' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: eng - title: Super Title - time_base: 1/1000 - - avg_frame_rate: 0/0 - bit_rate: '320000' - bits_per_sample: 0 - channel_layout: stereo - channels: 2 - codec_long_name: ATSC A/52A (AC-3) - codec_name: ac3 - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1/48000 - codec_type: audio - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - dmix_mode: '-1' - index: 3 - loro_cmixlev: '-1.000000' - loro_surmixlev: '-1.000000' - ltrt_cmixlev: '-1.000000' - ltrt_surmixlev: '-1.000000' - r_frame_rate: 0/0 - sample_fmt: fltp - sample_rate: '48000' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: eng - title: Super Title - time_base: 1/1000 - - avg_frame_rate: 0/0 - bits_per_sample: 0 - channel_layout: 5.1(side) - channels: 6 - codec_long_name: DCA (DTS Coherent Acoustics) - codec_name: dts - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1/48000 - codec_type: audio - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - index: 4 - profile: DTS-HD HRA - r_frame_rate: 0/0 - sample_fmt: fltp - sample_rate: '48000' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: fre - title: Super Title - time_base: 1/1000 - - avg_frame_rate: 0/0 - bit_rate: '1536000' - bits_per_sample: 0 - channel_layout: 5.1(side) - channels: 6 - codec_long_name: DCA (DTS Coherent Acoustics) - codec_name: dts - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1/48000 - codec_type: audio - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - index: 5 - profile: DTS - r_frame_rate: 0/0 - sample_fmt: fltp - sample_rate: '48000' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: fre - title: Super Title - time_base: 1/1000 - - avg_frame_rate: 0/0 - bit_rate: '640000' - bits_per_sample: 0 - channel_layout: 5.1(side) - channels: 6 - codec_long_name: ATSC A/52A (AC-3) - codec_name: ac3 - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1/48000 - codec_type: audio - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - dmix_mode: '-1' - index: 6 - loro_cmixlev: '-1.000000' - loro_surmixlev: '-1.000000' - ltrt_cmixlev: '-1.000000' - ltrt_surmixlev: '-1.000000' - r_frame_rate: 0/0 - sample_fmt: fltp - sample_rate: '48000' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: cze - title: Super Title - time_base: 1/1000 - - avg_frame_rate: 0/0 - bit_rate: '640000' - bits_per_sample: 0 - channel_layout: 5.1(side) - channels: 6 - codec_long_name: ATSC A/52A (AC-3) - codec_name: ac3 - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 1/48000 - codec_type: audio - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - dmix_mode: '-1' - index: 7 - loro_cmixlev: '-1.000000' - loro_surmixlev: '-1.000000' - ltrt_cmixlev: '-1.000000' - ltrt_surmixlev: '-1.000000' - r_frame_rate: 0/0 - sample_fmt: fltp - sample_rate: '48000' - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: hin - title: Super Title - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 8 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: eng - title: English-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 9 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: fre - title: French-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 10 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: cze - title: Czech-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 11 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: dut - title: Dutch-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 12 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: ara - title: Arabic-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 13 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: dan - title: Danish-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 14 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: fin - title: Finnish-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 15 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: nor - title: Norwegian-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 16 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: swe - title: Swedish-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 17 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: fre - title: French-FORCED-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 18 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: cze - title: Czech-FORCED-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - codec_long_name: HDMV Presentation Graphic Stream subtitles - codec_name: hdmv_pgs_subtitle - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: subtitle - disposition: - attached_pic: 0 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 503170 - index: 19 - r_frame_rate: 0/0 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - language: hin - title: Hindi-FORCED-PGS - time_base: 1/1000 - - avg_frame_rate: 0/0 - bits_per_raw_sample: '8' - chroma_location: center - codec_long_name: Motion JPEG - codec_name: mjpeg - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: video - coded_height: 600 - coded_width: 1067 - color_range: pc - color_space: bt470bg - display_aspect_ratio: 0:1 - disposition: - attached_pic: 1 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 745804800 - has_b_frames: 0 - height: 600 - index: 20 - level: -99 - pix_fmt: yuvj444p - r_frame_rate: 90000/1 - refs: 1 - sample_aspect_ratio: 0:1 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - filename: cover_land.jpg - mimetype: image/jpeg - time_base: 1/90000 - width: 1067 - - avg_frame_rate: 0/0 - bits_per_raw_sample: '8' - chroma_location: center - codec_long_name: Motion JPEG - codec_name: mjpeg - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: video - coded_height: 176 - coded_width: 120 - color_range: pc - color_space: bt470bg - display_aspect_ratio: 0:1 - disposition: - attached_pic: 1 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 745804800 - has_b_frames: 0 - height: 176 - index: 21 - level: -99 - pix_fmt: yuvj444p - r_frame_rate: 90000/1 - refs: 1 - sample_aspect_ratio: 0:1 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - filename: small_cover.jpg - mimetype: image/jpeg - time_base: 1/90000 - width: 120 - - avg_frame_rate: 0/0 - bits_per_raw_sample: '8' - chroma_location: center - codec_long_name: Motion JPEG - codec_name: mjpeg - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: video - coded_height: 120 - coded_width: 213 - color_range: pc - color_space: bt470bg - display_aspect_ratio: 0:1 - disposition: - attached_pic: 1 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 745804800 - has_b_frames: 0 - height: 120 - index: 22 - level: -99 - pix_fmt: yuvj444p - r_frame_rate: 90000/1 - refs: 1 - sample_aspect_ratio: 0:1 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - filename: small_cover_land.jpg - mimetype: image/jpeg - time_base: 1/90000 - width: 213 - - avg_frame_rate: 0/0 - bits_per_raw_sample: '8' - chroma_location: center - codec_long_name: Motion JPEG - codec_name: mjpeg - codec_tag: '0x0000' - codec_tag_string: '[0][0][0][0]' - codec_time_base: 0/1 - codec_type: video - coded_height: 882 - coded_width: 600 - color_range: pc - color_space: bt470bg - display_aspect_ratio: 0:1 - disposition: - attached_pic: 1 - clean_effects: 0 - comment: 0 - default: 0 - dub: 0 - forced: 0 - hearing_impaired: 0 - karaoke: 0 - lyrics: 0 - original: 0 - timed_thumbnails: 0 - visual_impaired: 0 - duration: '1:23:45.670000' - duration_ts: 745804800 - has_b_frames: 0 - height: 882 - index: 23 - level: -99 - pix_fmt: yuvj444p - r_frame_rate: 90000/1 - refs: 1 - sample_aspect_ratio: 0:1 - start_pts: 0 - start_time: '0:00:00.000000' - tags: - filename: cover.jpg - mimetype: image/jpeg - time_base: 1/90000 - width: 600 - -expected: - title: Super Title - path: videofile.mkv - duration: '1:23:45.670000' - size: 12345678901 byte - bit_rate: 1231233 bps - video: - - id: 0 - name: Super Title - width: 1920 pixel - height: 1080 pixel - scan_type: Progressive - aspect_ratio: 1.778 - pixel_aspect_ratio: 1.0 - resolution: 1080p - frame_rate: 23.976 FPS - bit_depth: 8 bit - codec: H.264 - profile: High - default: true - audio: - - id: 1 - name: Super Title - language: en - codec: DTS-HD - profile: Master Audio - channels_count: 8 - channels: '7.1' - bit_depth: 24 bit - sampling_rate: 48000 Hz - default: true - - id: 2 - name: Super Title - language: en - codec: DTS - channels_count: 6 - channels: '5.1' - bit_rate: 1536000 bps - sampling_rate: 48000 Hz - - id: 3 - name: Super Title - language: en - codec: AC-3 - channels_count: 2 - channels: '2.0' - bit_rate: 320000 bps - sampling_rate: 48000 Hz - - id: 4 - name: Super Title - language: fr - codec: DTS-HD - profile: High Resolution Audio - channels_count: 6 - channels: '5.1' - sampling_rate: 48000 Hz - - id: 5 - name: Super Title - language: fr - codec: DTS - channels_count: 6 - channels: '5.1' - bit_rate: 1536000 bps - sampling_rate: 48000 Hz - - id: 6 - name: Super Title - language: cs - codec: AC-3 - channels_count: 6 - channels: '5.1' - bit_rate: 640000 bps - sampling_rate: 48000 Hz - - id: 7 - name: Super Title - language: hi - codec: AC-3 - channels_count: 6 - channels: '5.1' - bit_rate: 640000 bps - sampling_rate: 48000 Hz - subtitle: - - id: 8 - name: English-PGS - language: en - format: PGS - - id: 9 - name: French-PGS - language: fr - format: PGS - - id: 10 - name: Czech-PGS - language: cs - format: PGS - - id: 11 - name: Dutch-PGS - language: nl - format: PGS - - id: 12 - name: Arabic-PGS - language: ar - format: PGS - - id: 13 - name: Danish-PGS - language: da - format: PGS - - id: 14 - name: Finnish-PGS - language: fi - format: PGS - - id: 15 - name: Norwegian-PGS - language: "no" - format: PGS - - id: 16 - name: Swedish-PGS - language: sv - format: PGS - - id: 17 - name: French-FORCED-PGS - language: fr - format: PGS - - id: 18 - name: Czech-FORCED-PGS - language: cs - format: PGS - - id: 19 - name: Hindi-FORCED-PGS - language: hi - format: PGS - provider: ffprobe From 9dbd763f8e0328dc447c7c09f5ad93fcdf03e039 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 14:28:22 +0100 Subject: [PATCH 076/102] Drop AudioCodecRule --- knowit/providers/ffmpeg.py | 5 +---- knowit/rules/__init__.py | 1 - knowit/rules/audio/__init__.py | 1 - knowit/rules/audio/codec.py | 11 ----------- 4 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 knowit/rules/audio/codec.py diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index cf4e149..a40f4b0 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -31,7 +31,6 @@ ) from knowit.rules import ( AudioChannelsRule, - AudioCodecRule, ClosedCaptionRule, HearingImpairedRule, LanguageRule, @@ -170,8 +169,7 @@ def __init__(self, config, suggested_path=None): 'name': Property('tags.title', description='audio track name'), 'language': Language('tags.language', description='audio language'), 'duration': Duration('duration', description='audio duration'), - 'codec': AudioCodec(config, 'codec_name', description='audio codec'), - '_codec': AudioCodec(config, 'profile', description='audio codec', private=True, reportable=False), + '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 @@ -200,7 +198,6 @@ def __init__(self, config, suggested_path=None): 'audio': { 'language': LanguageRule('audio language'), 'channels': AudioChannelsRule('audio channels'), - 'codec': AudioCodecRule('audio codec', override=True), }, 'subtitle': { 'language': LanguageRule('subtitle language'), diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index 8299e49..7eda2b0 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -1,7 +1,6 @@ from knowit.rules.audio import AtmosRule from knowit.rules.audio import AudioChannelsRule -from knowit.rules.audio import AudioCodecRule from knowit.rules.audio import DtsHdRule from knowit.rules.language import LanguageRule from knowit.rules.subtitle import ClosedCaptionRule diff --git a/knowit/rules/audio/__init__.py b/knowit/rules/audio/__init__.py index d0705eb..6f8dde3 100644 --- a/knowit/rules/audio/__init__.py +++ b/knowit/rules/audio/__init__.py @@ -1,5 +1,4 @@ from knowit.rules.audio.atmos import AtmosRule from knowit.rules.audio.channels import AudioChannelsRule -from knowit.rules.audio.codec import AudioCodecRule from knowit.rules.audio.dtshd import DtsHdRule diff --git a/knowit/rules/audio/codec.py b/knowit/rules/audio/codec.py deleted file mode 100644 index a6444dc..0000000 --- a/knowit/rules/audio/codec.py +++ /dev/null @@ -1,11 +0,0 @@ - -from knowit.rule import Rule - - -class AudioCodecRule(Rule): - """Audio Codec rule.""" - - def execute(self, props, pv_props, context): - """Execute the rule against properties.""" - if '_codec' in pv_props: - return pv_props.get('_codec') From 3ae19e9300d1ce372419bc89c8542eb690395346 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 14:48:54 +0100 Subject: [PATCH 077/102] Fixes AtmosRule --- knowit/providers/mediainfo.py | 3 ++- knowit/rules/audio/atmos.py | 29 ++++++++--------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 9bf08a0..2eff704 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -218,6 +218,7 @@ def __init__(self, config, suggested_path): 'duration': Duration('Duration', resolution=1000000, description='audio duration'), 'size': Quantity('StreamSize', unit=units.byte, description='audio stream size'), 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), + 'format_commercial': Property('Format_Commercial', private=True), 'profile': MultiValue(AudioProfile(config, 'Format_Profile', 'Format_AdditionalFeatures', description='audio codec profile'), delimiter=' / '), @@ -254,7 +255,7 @@ def __init__(self, config, suggested_path): 'audio': { 'language': LanguageRule('audio language'), 'channels': AudioChannelsRule('audio channels'), - '_atmosrule': AtmosRule('atmos rule'), + '_atmosrule': AtmosRule(config, 'atmos rule'), '_dtshdrule': DtsHdRule(config, 'dts-hd rule'), }, 'subtitle': { diff --git a/knowit/rules/audio/atmos.py b/knowit/rules/audio/atmos.py index 3c53fac..5b1d794 100644 --- a/knowit/rules/audio/atmos.py +++ b/knowit/rules/audio/atmos.py @@ -1,3 +1,4 @@ +import typing from knowit.rule import Rule @@ -5,27 +6,13 @@ class AtmosRule(Rule): """Atmos rule.""" - @classmethod - def _redefine(cls, props, name, index): - actual = props.get(name) - if isinstance(actual, list): - value = actual[index] - if value is None: - del props[name] - else: - props[name] = value + def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, **kwargs): + super().__init__(name, **kwargs) + self.audio_codecs = getattr(config, 'AudioCodec') def execute(self, props, pv_props, context): """Execute the rule against properties.""" - codecs = props.get('codec') or [] - # TODO: handle this properly - if 'atmos' in {codec.lower() for codec in codecs if codec}: - index = None - for i, codec in enumerate(codecs): - if codec and 'atmos' in codec.lower(): - index = i - break - - if index is not None: - for name in ('channels_count', 'sampling_rate'): - self._redefine(props, name, index) + profile = context.get('profile') or 'default' + format_commercial = pv_props.get('format_commercial') + if 'codec' in props and format_commercial and 'atmos' in format_commercial.lower(): + props['codec'] = [props['codec'], getattr(self.audio_codecs['ATMOS'], profile)] From e8fd7c1a3200c53e17a255a7aa42a25a5e3d5c47 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 14:57:33 +0100 Subject: [PATCH 078/102] Use milliseconds in duration --- knowit/properties/duration.py | 6 +++--- knowit/providers/mediainfo.py | 6 +++--- knowit/providers/mkvmerge.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 4f3d364..b6119c8 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -14,7 +14,7 @@ class Duration(Property[timedelta]): r'(?P\d{3})' r'(?P\d{3})?\d*)?') - def __init__(self, *args: str, resolution: float = 1, **kwargs): + def __init__(self, *args: str, resolution: int = 1, **kwargs): """Initialize a Duration.""" super().__init__(*args, **kwargs) self.resolution = resolution @@ -24,9 +24,9 @@ def handle(self, value, context: typing.MutableMapping): if isinstance(value, timedelta): return value elif isinstance(value, int): - return timedelta(microseconds=value * self.resolution) + return timedelta(milliseconds=value * self.resolution) try: - return timedelta(microseconds=int(float(value) * self.resolution)) + return timedelta(milliseconds=int(float(value) * self.resolution)) except ValueError: pass diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 2eff704..169a9b9 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -182,7 +182,7 @@ def __init__(self, config, suggested_path): 'general': { 'title': Property('Title', description='media title'), 'path': Property('CompleteName', description='media path'), - 'duration': Duration('Duration', resolution=1000000, description='media duration'), + 'duration': Duration('Duration', resolution=1000, description='media duration'), 'size': Quantity('FileSize', unit=units.byte, description='media size'), 'bit_rate': Quantity('OverallBitRate', unit=units.bps, description='media bit rate'), }, @@ -190,7 +190,7 @@ def __init__(self, config, suggested_path): 'id': Basic('ID', data_type=int, allow_fallback=True, description='video track number'), 'name': Property('Title', description='video track name'), 'language': Language('Language', description='video language'), - 'duration': Duration('Duration', resolution=1000000, description='video duration'), + 'duration': Duration('Duration', resolution=1000, description='video duration'), 'size': Quantity('StreamSize', unit=units.byte, description='video stream size'), 'width': Quantity('Width', unit=units.pixel), 'height': Quantity('Height', unit=units.pixel), @@ -215,7 +215,7 @@ def __init__(self, config, suggested_path): 'id': Basic('ID', data_type=int, allow_fallback=True, description='audio track number'), 'name': Property('Title', description='audio track name'), 'language': Language('Language', description='audio language'), - 'duration': Duration('Duration', resolution=1000000, description='audio duration'), + 'duration': Duration('Duration', resolution=1000, description='audio duration'), 'size': Quantity('StreamSize', unit=units.byte, description='audio stream size'), 'codec': MultiValue(AudioCodec(config, 'CodecID', description='audio codec')), 'format_commercial': Property('Format_Commercial', private=True), diff --git a/knowit/providers/mkvmerge.py b/knowit/providers/mkvmerge.py index 41cce2c..67f1f1a 100644 --- a/knowit/providers/mkvmerge.py +++ b/knowit/providers/mkvmerge.py @@ -122,7 +122,7 @@ def __init__(self, config, suggested_path=None, *args, **kwargs): super().__init__(config, { 'general': { 'title': Property('title', description='media title'), - 'duration': Duration('duration', resolution=0.001, description='media duration'), + 'duration': Duration('duration', description='media duration'), }, 'video': { 'id': Basic('number', data_type=int, description='video track number'), From 86e106534f1fb75b4010310218e69e308175f8ab Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 15:20:07 +0100 Subject: [PATCH 079/102] Fixes output not using 'default' profile. Fixes yaml output not preserving order --- knowit/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/knowit/__main__.py b/knowit/__main__.py index 25ddf89..93a2331 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -142,6 +142,7 @@ def _as_yaml( Dumper=get_yaml_dumper(context), default_flow_style=False, allow_unicode=True, + sort_keys=False, ) @@ -192,7 +193,7 @@ def main(args: typing.List[str] = None) -> None: report: typing.MutableMapping[str, str] = {} for i, video_path in enumerate(paths): try: - context = dict(vars(options)) + context = {k: v for k, v in vars(options).items() if v is not None} if options.report: context['report'] = report else: From d0e637aa294e1cb71af598276f9087654f9bbbb6 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 16:26:06 +0100 Subject: [PATCH 080/102] Get rid of float imprecision --- knowit/properties/basic.py | 11 +++++++---- knowit/properties/duration.py | 5 +++-- knowit/properties/quantity.py | 4 ++++ knowit/properties/video/ratio.py | 10 ++++++---- knowit/providers/mediainfo.py | 13 +++++++++---- knowit/rules/audio/channels.py | 6 +++--- knowit/rules/video/resolution.py | 3 ++- knowit/serializer.py | 11 +++++++---- knowit/utils.py | 11 +++++++++++ 9 files changed, 52 insertions(+), 22 deletions(-) diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index 462c33e..caf3e91 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -6,21 +6,24 @@ class Basic(Property[T]): - """Basic property to handle int, float and other basic types.""" + """Basic property to handle int, Decimal and other basic types.""" - def __init__(self, *args: str, data_type: typing.Type, allow_fallback: bool = False, **kwargs): + def __init__(self, *args: str, data_type: typing.Type, + processor: typing.Optional[typing.Callable[[T], T]] = None, + allow_fallback: bool = False, **kwargs): """Init method.""" super().__init__(*args, **kwargs) self.data_type = data_type + self.processor = processor or (lambda x: x) self.allow_fallback = allow_fallback def handle(self, value, context: typing.MutableMapping): """Handle value.""" if isinstance(value, self.data_type): - return value + return self.processor(value) try: - return self.data_type(value) + return self.processor(self.data_type(value)) except ValueError: if not self.allow_fallback: self.report(value, context) diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index b6119c8..53b611a 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -1,6 +1,7 @@ import re import typing from datetime import timedelta +from decimal import Decimal, InvalidOperation from knowit.property import Property @@ -26,8 +27,8 @@ def handle(self, value, context: typing.MutableMapping): elif isinstance(value, int): return timedelta(milliseconds=value * self.resolution) try: - return timedelta(milliseconds=int(float(value) * self.resolution)) - except ValueError: + return timedelta(milliseconds=int(Decimal(value) * self.resolution)) + except (ValueError, InvalidOperation): pass match = self.duration_re.match(value) diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index b1c2a3b..37885d4 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -1,5 +1,7 @@ +from decimal import Decimal from knowit.property import Property +from knowit.utils import round_decimal class Quantity(Property): @@ -19,5 +21,7 @@ def handle(self, value, context): except ValueError: self.report(value, context) return + if isinstance(value, Decimal): + value = round_decimal(value, min_digits=1, max_digits=3) return value if context.get('no_units') else value * self.unit diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index 34930f7..605c2a4 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -1,11 +1,13 @@ import re import typing +from decimal import Decimal from knowit.property import Property +from knowit.utils import round_decimal -class Ratio(Property[float]): +class Ratio(Property[Decimal]): """Ratio property.""" def __init__(self, *args: str, unit=None, **kwargs): @@ -15,15 +17,15 @@ def __init__(self, *args: str, unit=None, **kwargs): ratio_re = re.compile(r'(?P\d+)[:/](?P\d+)') - def handle(self, value, context) -> typing.Optional[float]: + def handle(self, value, context) -> typing.Optional[Decimal]: """Handle ratio.""" match = self.ratio_re.match(value) if match: width, height = match.groups() if (width, height) == ('0', '1'): # identity - return 1. + return Decimal('1.0') - result = round(float(width) / float(height), 3) + result = round_decimal(Decimal(width) / Decimal(height), min_digits=1, max_digits=3) if self.unit: result *= self.unit diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 169a9b9..ef4e90c 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -2,6 +2,7 @@ import json import re from ctypes import c_void_p, c_wchar_p +from decimal import Decimal from logging import DEBUG, NullHandler, getLogger from subprocess import CalledProcessError, check_output @@ -47,7 +48,7 @@ from ..units import units from ..utils import ( define_candidate, - detect_os, + detect_os, round_decimal, ) logger = getLogger(__name__) @@ -195,10 +196,14 @@ def __init__(self, config, suggested_path): 'width': Quantity('Width', unit=units.pixel), 'height': Quantity('Height', unit=units.pixel), 'scan_type': ScanType(config, 'ScanType', default='Progressive', description='video scan type'), - 'aspect_ratio': Basic('DisplayAspectRatio', data_type=float, description='display aspect ratio'), - 'pixel_aspect_ratio': Basic('PixelAspectRatio', data_type=float, description='pixel aspect ratio'), + 'aspect_ratio': Basic('DisplayAspectRatio', data_type=Decimal, + processor=lambda x: round_decimal(x, min_digits=1, max_digits=3), + description='display aspect ratio'), + 'pixel_aspect_ratio': Basic('PixelAspectRatio', data_type=Decimal, + processor=lambda x: round_decimal(x, min_digits=1, max_digits=3), + description='pixel aspect ratio'), 'resolution': None, # populated with ResolutionRule - 'frame_rate': Quantity('FrameRate', unit=units.FPS, data_type=float, description='video frame rate'), + 'frame_rate': Quantity('FrameRate', unit=units.FPS, data_type=Decimal, description='video frame rate'), # frame_rate_mode 'bit_rate': Quantity('BitRate', unit=units.bps, description='video bit rate'), 'bit_depth': Quantity('BitDepth', unit=units.bit, description='video bit depth'), diff --git a/knowit/rules/audio/channels.py b/knowit/rules/audio/channels.py index 5a85315..a4dcf6d 100644 --- a/knowit/rules/audio/channels.py +++ b/knowit/rules/audio/channels.py @@ -1,4 +1,4 @@ - +from decimal import Decimal from logging import NullHandler, getLogger from knowit.rule import Rule @@ -31,10 +31,10 @@ def execute(self, props, pv_props, context): if not position: continue - c = 0 + c = Decimal('0.0') for i in position.split('/'): try: - c += float(i) + c += Decimal(i) except ValueError: logger.debug('Invalid %s: %s', self.description, i) pass diff --git a/knowit/rules/video/resolution.py b/knowit/rules/video/resolution.py index 5a2ac81..61f8272 100644 --- a/knowit/rules/video/resolution.py +++ b/knowit/rules/video/resolution.py @@ -1,3 +1,4 @@ +from decimal import Decimal from knowit.rule import Rule @@ -45,7 +46,7 @@ def execute(self, props, pv_props, context): except AttributeError: pass - dar = props.get('aspect_ratio', float(width) / height) + dar = props.get('aspect_ratio', Decimal(width) / height) par = props.get('pixel_aspect_ratio', 1) scan_type = props.get('scan_type', 'p')[0].lower() diff --git a/knowit/serializer.py b/knowit/serializer.py index da4911d..87518ed 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -2,6 +2,7 @@ import json import typing from datetime import timedelta +from decimal import Decimal import babelfish import yaml @@ -43,8 +44,8 @@ def default_representer(self, data): """Convert data to string.""" if isinstance(data, int): return self.represent_int(data) - if isinstance(data, float): - return self.represent_float(data) + if isinstance(data, Decimal): + return self.represent_scalar('tag:yaml.org,2002:float', str(data).lower()) return self.represent_str(str(data)) def default_language_representer(self, data): @@ -62,6 +63,7 @@ def default_duration_representer(self, data): CustomDumper.add_representer(babelfish.Language, CustomDumper.default_language_representer) CustomDumper.add_representer(timedelta, CustomDumper.default_duration_representer) CustomDumper.add_representer(units.Quantity, CustomDumper.default_quantity_representer) + CustomDumper.add_representer(Decimal, CustomDumper.default_representer) return CustomDumper @@ -85,13 +87,14 @@ class CustomLoader(yaml.Loader): def format_duration( duration: datetime.timedelta, profile='default', -) -> typing.Union[str, float]: +) -> typing.Union[str, Decimal]: if profile == 'technical': return str(duration) seconds = duration.total_seconds() if profile == 'code': - return duration.total_seconds() + value = Decimal((duration.days * 86400 + duration.seconds) * 10 ** 6 + duration.microseconds) / 10**6 + return value hours = int(seconds // 3600) seconds = seconds - (hours * 3600) diff --git a/knowit/utils.py b/knowit/utils.py index 1e06a64..ce525a8 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -1,6 +1,7 @@ import os import sys import typing +from decimal import Decimal from knowit import VIDEO_EXTENSIONS @@ -118,3 +119,13 @@ def build_path_candidates( for path in paths for name in names ) + + +def round_decimal(value: Decimal, min_digits=0, max_digits: typing.Optional[int] = None): + decimal_places = abs(value.normalize().as_tuple().exponent) + if decimal_places <= min_digits: + return round(value, min_digits) + if max_digits: + return round(value, min(max_digits, decimal_places)) + return value + From a77e0ba31d9a07a330fd2533c6da46713d1d93f9 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 16:42:07 +0100 Subject: [PATCH 081/102] Fixes round_decimal --- knowit/serializer.py | 6 ++++-- knowit/utils.py | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/knowit/serializer.py b/knowit/serializer.py index 87518ed..c8db442 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -8,6 +8,7 @@ import yaml from knowit.units import units +from knowit.utils import round_decimal def format_property(profile: str, o): @@ -93,8 +94,9 @@ def format_duration( seconds = duration.total_seconds() if profile == 'code': - value = Decimal((duration.days * 86400 + duration.seconds) * 10 ** 6 + duration.microseconds) / 10**6 - return value + return round_decimal( + Decimal((duration.days * 86400 + duration.seconds) * 10 ** 6 + duration.microseconds) / 10**6, min_digits=1 + ) hours = int(seconds // 3600) seconds = seconds - (hours * 3600) diff --git a/knowit/utils.py b/knowit/utils.py index ce525a8..6845fb0 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -122,7 +122,11 @@ def build_path_candidates( def round_decimal(value: Decimal, min_digits=0, max_digits: typing.Optional[int] = None): - decimal_places = abs(value.normalize().as_tuple().exponent) + exponent = value.normalize().as_tuple().exponent + if exponent >= 0: + return round(value, min_digits) + + decimal_places = abs(exponent) if decimal_places <= min_digits: return round(value, min_digits) if max_digits: From 05ac4b6c7088713257730988c45af36f46cfd780 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 17:32:48 +0100 Subject: [PATCH 082/102] Use Decimal instead of float in pyyaml loader --- knowit/serializer.py | 49 +++++++++++++++++++++++++++++++++++++++----- tests/__init__.py | 1 - 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/knowit/serializer.py b/knowit/serializer.py index c8db442..4922dc7 100644 --- a/knowit/serializer.py +++ b/knowit/serializer.py @@ -1,11 +1,18 @@ import datetime import json +import re import typing from datetime import timedelta from decimal import Decimal import babelfish import yaml +from yaml.composer import Composer +from yaml.constructor import SafeConstructor +from yaml.parser import Parser +from yaml.reader import Reader +from yaml.resolver import Resolver as DefaultResolver +from yaml.scanner import Scanner from knowit.units import units from knowit.utils import round_decimal @@ -45,8 +52,6 @@ def default_representer(self, data): """Convert data to string.""" if isinstance(data, int): return self.represent_int(data) - if isinstance(data, Decimal): - return self.represent_scalar('tag:yaml.org,2002:float', str(data).lower()) return self.represent_str(str(data)) def default_language_representer(self, data): @@ -72,16 +77,50 @@ def default_duration_representer(self, data): def get_yaml_loader(constructors=None): """Return a yaml loader that handles sequences as python lists.""" constructors = constructors or {} + yaml_implicit_resolvers = dict(DefaultResolver.yaml_implicit_resolvers) - class CustomLoader(yaml.Loader): + class Resolver(DefaultResolver): + """Custom YAML Resolver.""" + + Resolver.yaml_implicit_resolvers.clear() + for ch, vs in yaml_implicit_resolvers.items(): + Resolver.yaml_implicit_resolvers.setdefault(ch, []).extend( + (tag, regexp) for tag, regexp in vs + if not tag.endswith('float') + ) + Resolver.add_implicit_resolver( # regex copied from yaml source + '!decimal', + re.compile(r'''^(?: + [-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + |\.[0-9_]+(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-9]?[0-9])+\.[0-9_]* + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN) + )$''', re.VERBOSE), + list('-+0123456789.') + ) + + class CustomLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): """Custom YAML Loader.""" - pass + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + SafeConstructor.__init__(self) + Resolver.__init__(self) - CustomLoader.add_constructor('tag:yaml.org,2002:seq', CustomLoader.construct_python_tuple) + CustomLoader.add_constructor('tag:yaml.org,2002:seq', yaml.Loader.construct_python_tuple) for tag, constructor in constructors.items(): CustomLoader.add_constructor(tag, constructor) + def decimal_constructor(loader, node): + value = loader.construct_scalar(node) + return Decimal(value) + + CustomLoader.add_constructor('!decimal', decimal_constructor) + return CustomLoader diff --git a/tests/__init__.py b/tests/__init__.py index 6439727..1977c40 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,7 +7,6 @@ from io import BytesIO from zipfile import ZipFile -import babelfish import requests import yaml from yaml.constructor import Constructor From 9b2672326778a17b1e37bcec7893289963c6d381 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 18:12:00 +0100 Subject: [PATCH 083/102] Added missing properties to mkvmerge provider + tests --- knowit/properties/__init__.py | 1 + knowit/properties/duration.py | 4 +- knowit/properties/video/__init__.py | 1 + knowit/properties/video/dimensions.py | 25 ++++ knowit/providers/mkvmerge.py | 10 +- tests/data/mkvmerge/media_001.mkv.json | 186 +++++++++++++++++++++++++ tests/data/mkvmerge/media_001.mkv.yml | 47 +++++++ tests/data/mkvmerge/test1.mkv.yml | 22 +++ tests/data/mkvmerge/test2.mkv.yml | 23 +++ tests/data/mkvmerge/test3.mkv.yml | 23 +++ tests/data/mkvmerge/test4.mkv.yml | 21 +++ tests/data/mkvmerge/test5.mkv.yml | 47 +++++++ tests/data/mkvmerge/test6.mkv.yml | 20 +++ tests/data/mkvmerge/test7.mkv.yml | 21 +++ tests/data/mkvmerge/test8.mkv.yml | 21 +++ tests/test_mkvmerge.py | 34 +++++ 16 files changed, 501 insertions(+), 5 deletions(-) create mode 100644 knowit/properties/video/dimensions.py create mode 100644 tests/data/mkvmerge/media_001.mkv.json create mode 100644 tests/data/mkvmerge/media_001.mkv.yml create mode 100644 tests/data/mkvmerge/test1.mkv.yml create mode 100644 tests/data/mkvmerge/test2.mkv.yml create mode 100644 tests/data/mkvmerge/test3.mkv.yml create mode 100644 tests/data/mkvmerge/test4.mkv.yml create mode 100644 tests/data/mkvmerge/test5.mkv.yml create mode 100644 tests/data/mkvmerge/test6.mkv.yml create mode 100644 tests/data/mkvmerge/test7.mkv.yml create mode 100644 tests/data/mkvmerge/test8.mkv.yml create mode 100644 tests/test_mkvmerge.py diff --git a/knowit/properties/__init__.py b/knowit/properties/__init__.py index 00771b8..13bd9ba 100644 --- a/knowit/properties/__init__.py +++ b/knowit/properties/__init__.py @@ -17,6 +17,7 @@ Ratio, ScanType, VideoCodec, + VideoDimensions, VideoEncoder, VideoProfile, VideoProfileLevel, diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index 53b611a..a39b83d 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -15,7 +15,7 @@ class Duration(Property[timedelta]): r'(?P\d{3})' r'(?P\d{3})?\d*)?') - def __init__(self, *args: str, resolution: int = 1, **kwargs): + def __init__(self, *args: str, resolution: int or Decimal = 1, **kwargs): """Initialize a Duration.""" super().__init__(*args, **kwargs) self.resolution = resolution @@ -25,7 +25,7 @@ def handle(self, value, context: typing.MutableMapping): if isinstance(value, timedelta): return value elif isinstance(value, int): - return timedelta(milliseconds=value * self.resolution) + return timedelta(milliseconds=int(value * self.resolution)) try: return timedelta(milliseconds=int(Decimal(value) * self.resolution)) except (ValueError, InvalidOperation): diff --git a/knowit/properties/video/__init__.py b/knowit/properties/video/__init__.py index 5cd2836..efcf5b4 100644 --- a/knowit/properties/video/__init__.py +++ b/knowit/properties/video/__init__.py @@ -1,5 +1,6 @@ from knowit.properties.video.codec import VideoCodec +from knowit.properties.video.dimensions import VideoDimensions from knowit.properties.video.encoder import VideoEncoder from knowit.properties.video.profile import VideoProfile from knowit.properties.video.profile import VideoProfileLevel diff --git a/knowit/properties/video/dimensions.py b/knowit/properties/video/dimensions.py new file mode 100644 index 0000000..0a1b595 --- /dev/null +++ b/knowit/properties/video/dimensions.py @@ -0,0 +1,25 @@ + +import re +import typing + +from knowit.property import Property + + +class VideoDimensions(Property[int]): + """Dimensions property.""" + + def __init__(self, *args: str, dimension='width' or 'height', **kwargs): + """Initialize the object.""" + super().__init__(*args, **kwargs) + self.dimension = dimension + + dimensions_re = re.compile(r'(?P\d+)x(?P\d+)') + + def handle(self, value, context) -> typing.Optional[int]: + """Handle ratio.""" + match = self.dimensions_re.match(value) + if match: + return int(match.groupdict().get(self.dimension)) + + self.report(value, context) + return None diff --git a/knowit/providers/mkvmerge.py b/knowit/providers/mkvmerge.py index 67f1f1a..a0e3b49 100644 --- a/knowit/providers/mkvmerge.py +++ b/knowit/providers/mkvmerge.py @@ -2,6 +2,7 @@ import json import logging import re +from decimal import Decimal from logging import NullHandler, getLogger from subprocess import check_output @@ -12,6 +13,7 @@ Language, Quantity, VideoCodec, + VideoDimensions, YesNo, ) from knowit.property import Property @@ -122,15 +124,16 @@ def __init__(self, config, suggested_path=None, *args, **kwargs): super().__init__(config, { 'general': { 'title': Property('title', description='media title'), - 'duration': Duration('duration', description='media duration'), + 'duration': Duration('duration', resolution=Decimal('0.000001'), description='media duration'), }, 'video': { 'id': Basic('number', data_type=int, description='video track number'), 'name': Property('name', description='video track name'), 'language': Language('language_ietf', 'language', description='video language'), - 'width': Quantity('width', unit=units.pixel), # TODO: extract resolution - 'height': Quantity('height', unit=units.pixel), + 'width': VideoDimensions('display_dimensions', dimension='width'), + 'height': VideoDimensions('display_dimensions', dimension='height'), 'scan_type': YesNo('interlaced', yes='Interlaced', no='Progressive', default='Progressive', + config=config, config_key='ScanType', description='video scan type'), 'resolution': None, # populated with ResolutionRule # 'bit_depth', Property('bit_depth', Integer('video bit depth')), @@ -146,6 +149,7 @@ def __init__(self, config, suggested_path=None, *args, **kwargs): 'codec': AudioCodec(config, 'codec_id', description='audio codec'), 'channels_count': Basic('audio_channels', data_type=int, description='audio channels count'), 'channels': None, # populated with AudioChannelsRule + 'sampling_rate': Quantity('audio_sampling_frequency', unit=units.Hz, description='audio sampling rate'), 'forced': YesNo('forced_track', hide_value=False, description='audio track forced'), 'default': YesNo('default_track', hide_value=False, description='audio track default'), 'enabled': YesNo('enabled_track', hide_value=True, description='audio track enabled'), diff --git a/tests/data/mkvmerge/media_001.mkv.json b/tests/data/mkvmerge/media_001.mkv.json new file mode 100644 index 0000000..90f2fd6 --- /dev/null +++ b/tests/data/mkvmerge/media_001.mkv.json @@ -0,0 +1,186 @@ +{ + "attachments": [], + "chapters": [ + { + "num_entries": 8 + } + ], + "container": { + "properties": { + "container_type": 17, + "date_local": "2020-11-11T07:35:31+01:00", + "date_utc": "2020-11-11T06:35:31Z", + "duration": 3428352000000, + "is_providing_timestamps": true, + "muxing_application": "libebml v1.4.0 + libmatroska v1.6.2", + "segment_uid": "5c5db0c93c7ebac3c88f7d372288b20e", + "title": "Media 001", + "writing_application": "mkvmerge v51.0.0 ('I Wish') 64-bit" + }, + "recognized": true, + "supported": true, + "type": "Matroska" + }, + "errors": [], + "file_name": "tests/data/mkvmerge/media_001.mkv", + "global_tags": [], + "identification_format_version": 14, + "track_tags": [], + "tracks": [ + { + "codec": "HEVC/H.265/MPEG-H", + "id": 0, + "properties": { + "codec_id": "V_MPEGH/ISO/HEVC", + "codec_private_data": "012220000000b0000000000099f000fcfdfafa00000f04600001002240010c01ffff222000000300b0000003000003009914860300000303e900005dc050610001003d420101222000000300b00000030000030099a001e020021c4db1486924294af016a1220136c2000007d20000bb80c5781cdc0004eef80009ddf7cf1e3d62000100074401c172f63b64670002001e4e01891821349baa199608fc8a4839083d13404200989680000003001480000a4e019004000003000080", + "codec_private_length": 187, + "default_duration": 41708333, + "default_track": false, + "display_dimensions": "3840x2160", + "display_unit": 0, + "enabled_track": true, + "forced_track": false, + "language": "eng", + "language_ietf": "en", + "minimum_timestamp": 0, + "number": 1, + "packetizer": "mpegh_p2_video", + "pixel_dimensions": "3840x2160", + "uid": 1 + }, + "type": "video" + }, + { + "codec": "TrueHD Atmos", + "id": 1, + "properties": { + "audio_channels": 8, + "audio_sampling_frequency": 48000, + "codec_id": "A_TRUEHD", + "codec_private_length": 0, + "default_track": true, + "enabled_track": true, + "forced_track": false, + "language": "eng", + "language_ietf": "en", + "minimum_timestamp": 0, + "number": 2, + "uid": 2 + }, + "type": "audio" + }, + { + "codec": "AC-3 Dolby Surround EX", + "id": 2, + "properties": { + "audio_channels": 6, + "audio_sampling_frequency": 48000, + "codec_id": "A_AC3", + "codec_private_length": 0, + "default_duration": 32000000, + "default_track": false, + "enabled_track": true, + "forced_track": false, + "language": "eng", + "language_ietf": "en", + "minimum_timestamp": 0, + "number": 3, + "uid": 3 + }, + "type": "audio" + }, + { + "codec": "AC-3", + "id": 3, + "properties": { + "audio_channels": 6, + "audio_sampling_frequency": 48000, + "codec_id": "A_AC3", + "codec_private_length": 0, + "default_duration": 32000000, + "default_track": false, + "enabled_track": true, + "forced_track": false, + "language": "ger", + "language_ietf": "de", + "minimum_timestamp": 0, + "number": 4, + "uid": 4 + }, + "type": "audio" + }, + { + "codec": "DTS", + "id": 4, + "properties": { + "audio_bits_per_sample": 24, + "audio_channels": 2, + "audio_sampling_frequency": 48000, + "codec_id": "A_DTS", + "codec_private_length": 0, + "default_duration": 10666667, + "default_track": false, + "enabled_track": true, + "forced_track": false, + "language": "por", + "language_ietf": "pt-BR", + "minimum_timestamp": 2002000000, + "number": 5, + "uid": 5 + }, + "type": "audio" + }, + { + "codec": "HDMV PGS", + "id": 5, + "properties": { + "codec_id": "S_HDMV/PGS", + "codec_private_length": 0, + "content_encoding_algorithms": "0", + "default_track": false, + "enabled_track": true, + "forced_track": false, + "language": "eng", + "language_ietf": "en", + "number": 6, + "uid": 6 + }, + "type": "subtitles" + }, + { + "codec": "HDMV PGS", + "id": 6, + "properties": { + "codec_id": "S_HDMV/PGS", + "codec_private_length": 0, + "content_encoding_algorithms": "0", + "default_track": false, + "enabled_track": true, + "forced_track": false, + "language": "ger", + "language_ietf": "de", + "number": 7, + "uid": 11 + }, + "type": "subtitles" + }, + { + "codec": "HDMV PGS", + "id": 7, + "properties": { + "codec_id": "S_HDMV/PGS", + "codec_private_length": 0, + "content_encoding_algorithms": "0", + "default_track": false, + "enabled_track": true, + "forced_track": false, + "language": "por", + "language_ietf": "pt-BR", + "number": 8, + "uid": 14 + }, + "type": "subtitles" + } + ], + "warnings": [] +} diff --git a/tests/data/mkvmerge/media_001.mkv.yml b/tests/data/mkvmerge/media_001.mkv.yml new file mode 100644 index 0000000..2d4731f --- /dev/null +++ b/tests/data/mkvmerge/media_001.mkv.yml @@ -0,0 +1,47 @@ +title: Media 001 +duration: '0:57:08.352000' +path: tests/data/mkvmerge/media_001.mkv +container: mkv +video: +- id: 1 + language: en + width: 3840 + height: 2160 + resolution: 2160p + scan_type: PROGRESSIVE + codec: H265 +audio: +- id: 2 + language: en + codec: TRUEHD + channels_count: 8 + channels: 7.1 + sampling_rate: 48000 + default: true +- id: 3 + language: en + codec: AC3 + channels_count: 6 + channels: 5.1 + sampling_rate: 48000 +- id: 4 + language: de + codec: AC3 + channels_count: 6 + channels: 5.1 + sampling_rate: 48000 +- id: 5 + language: pt-BR + codec: DTS + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 +subtitle: +- id: 6 + language: en +- id: 7 + language: de +- id: 8 + language: pt-BR +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test1.mkv.yml b/tests/data/mkvmerge/test1.mkv.yml new file mode 100644 index 0000000..d203d77 --- /dev/null +++ b/tests/data/mkvmerge/test1.mkv.yml @@ -0,0 +1,22 @@ +duration: 87.336 +path: tests/data/videos/test1.mkv +container: mkv +size: 23339337 +video: +- id: 1 + language: und + width: 854 + height: 480 + scan_type: PROGRESSIVE + resolution: 480p + default: true +audio: +- id: 2 + language: und + codec: MP3 + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 + default: true +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test2.mkv.yml b/tests/data/mkvmerge/test2.mkv.yml new file mode 100644 index 0000000..8ec7a3f --- /dev/null +++ b/tests/data/mkvmerge/test2.mkv.yml @@ -0,0 +1,23 @@ +duration: 47.509 +path: tests/data/videos/test2.mkv +container: mkv +size: 21142764 +video: +- id: 1 + language: und + width: 1354 + height: 576 + scan_type: PROGRESSIVE + resolution: 1080p + codec: H264 + default: true +audio: +- id: 2 + language: und + codec: AAC + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 + default: true +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test3.mkv.yml b/tests/data/mkvmerge/test3.mkv.yml new file mode 100644 index 0000000..e3c7dc7 --- /dev/null +++ b/tests/data/mkvmerge/test3.mkv.yml @@ -0,0 +1,23 @@ +duration: 49.064 +path: tests/data/videos/test3.mkv +container: mkv +size: 21061472 +video: +- id: 1 + language: und + width: 1024 + height: 576 + scan_type: PROGRESSIVE + resolution: 576p + codec: H264 + default: true +audio: +- id: 2 + language: en + codec: MP3 + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 + default: true +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test4.mkv.yml b/tests/data/mkvmerge/test4.mkv.yml new file mode 100644 index 0000000..0ad3c96 --- /dev/null +++ b/tests/data/mkvmerge/test4.mkv.yml @@ -0,0 +1,21 @@ +path: tests/data/videos/test4.mkv +container: mkv +size: 21313902 +video: +- id: 1 + language: und + width: 1280 + height: 720 + scan_type: PROGRESSIVE + resolution: 720p + default: true +audio: +- id: 2 + language: und + codec: VORBIS + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 + default: true +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test5.mkv.yml b/tests/data/mkvmerge/test5.mkv.yml new file mode 100644 index 0000000..5700ec7 --- /dev/null +++ b/tests/data/mkvmerge/test5.mkv.yml @@ -0,0 +1,47 @@ +duration: 46.665 +path: tests/data/videos/test5.mkv +container: mkv +size: 31762747 +video: +- id: 1 + language: und + width: 1024 + height: 576 + scan_type: PROGRESSIVE + resolution: 576p + codec: H264 + default: true +audio: +- id: 2 + language: und + codec: AAC + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 + default: true +- id: 10 + language: en + codec: AAC + channels_count: 1 + channels: 1.0 + sampling_rate: 22050 +subtitle: +- id: 3 + language: en + default: true +- id: 4 + language: hu +- id: 5 + language: de +- id: 6 + language: fr +- id: 8 + language: es +- id: 9 + language: it +- id: 11 + language: ja +- id: 7 + language: und +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test6.mkv.yml b/tests/data/mkvmerge/test6.mkv.yml new file mode 100644 index 0000000..e4d3dc6 --- /dev/null +++ b/tests/data/mkvmerge/test6.mkv.yml @@ -0,0 +1,20 @@ +duration: 87.336 +path: tests/data/videos/test6.mkv +container: mkv +size: 23343928 +video: +- id: 1 + language: und + width: 854 + height: 480 + scan_type: PROGRESSIVE + resolution: 480p +audio: +- id: 2 + language: und + codec: MP3 + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test7.mkv.yml b/tests/data/mkvmerge/test7.mkv.yml new file mode 100644 index 0000000..43c3f04 --- /dev/null +++ b/tests/data/mkvmerge/test7.mkv.yml @@ -0,0 +1,21 @@ +duration: 37.043 +path: tests/data/videos/test7.mkv +container: mkv +size: 21848518 +video: +- id: 1 + language: und + width: 1024 + height: 576 + scan_type: PROGRESSIVE + resolution: 576p + codec: H264 +audio: +- id: 2 + language: und + codec: AAC + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 +provider: + name: mkvmerge diff --git a/tests/data/mkvmerge/test8.mkv.yml b/tests/data/mkvmerge/test8.mkv.yml new file mode 100644 index 0000000..fe87ab9 --- /dev/null +++ b/tests/data/mkvmerge/test8.mkv.yml @@ -0,0 +1,21 @@ +duration: 47.341 +path: tests/data/videos/test8.mkv +container: mkv +size: 21224737 +video: +- id: 1 + language: und + width: 1024 + height: 576 + scan_type: PROGRESSIVE + resolution: 576p + codec: H264 +audio: +- id: 2 + language: und + codec: AAC + channels_count: 2 + channels: 2.0 + sampling_rate: 48000 +provider: + name: mkvmerge diff --git a/tests/test_mkvmerge.py b/tests/test_mkvmerge.py new file mode 100644 index 0000000..fdb1885 --- /dev/null +++ b/tests/test_mkvmerge.py @@ -0,0 +1,34 @@ + +import pytest + +from knowit import know + +from . import ( + assert_expected, + id_func, + mediafiles +) + + +@pytest.mark.parametrize('media', mediafiles.get_json_media('mkvmerge'), ids=id_func) +def test_mkvmerge_provider(mkvmerge, media, options): + # Given + mkvmerge[media.video_path] = media.input_data + + # When + actual = know(media.video_path, options) + + # Then + assert_expected(media.expected_data, actual, options) + + +@pytest.mark.parametrize('media', mediafiles.get_real_media('mkvmerge'), ids=id_func) +def test_mkvmerge_provider_real_media(media, options): + # Given + options['provider'] = 'mkvmerge' + + # When + actual = know(media.video_path, options) + + # Then + assert_expected(media.expected_data, actual, options) From f4df8b1a9ba3591c52d80c1e833746dca9bbb1c7 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 18:18:50 +0100 Subject: [PATCH 084/102] Add additional test data for mediainfo provider --- tests/data/mediainfo/media_001.mkv.json | 750 ++++++++++++++++++++++++ tests/data/mediainfo/media_001.mkv.yml | 85 +++ 2 files changed, 835 insertions(+) create mode 100644 tests/data/mediainfo/media_001.mkv.json create mode 100644 tests/data/mediainfo/media_001.mkv.yml diff --git a/tests/data/mediainfo/media_001.mkv.json b/tests/data/mediainfo/media_001.mkv.json new file mode 100644 index 0000000..2a91d87 --- /dev/null +++ b/tests/data/mediainfo/media_001.mkv.json @@ -0,0 +1,750 @@ +{ +"media": { +"@ref": "tests/data/mediainfo/media_001.mkv", +"track": [ +{ +"@type": "General", +"Count": "331", +"StreamCount": "1", +"StreamKind": "General", +"StreamKind_String": "General", +"StreamKindID": "0", +"UniqueID": "114787942512835844181888359558945472299", +"UniqueID_String": "114787942512835844181888359558945472299 (0x565B5A83D37D47C81A003968DBF87F2B)", +"VideoCount": "1", +"AudioCount": "4", +"TextCount": "3", +"MenuCount": "1", +"Video_Format_List": "HEVC", +"Video_Format_WithHint_List": "HEVC", +"Video_Codec_List": "HEVC", +"Video_Language_List": "English", +"Audio_Format_List": "MLP FBA 16-ch / AC-3 / AC-3 / DTS", +"Audio_Format_WithHint_List": "MLP FBA 16-ch / AC-3 / AC-3 / DTS", +"Audio_Codec_List": "MLP FBA 16-ch / AC-3 / AC-3 / DTS", +"Audio_Language_List": "English / English / German / Portuguese", +"Text_Format_List": "PGS / PGS / PGS", +"Text_Format_WithHint_List": "PGS / PGS / PGS", +"Text_Codec_List": "PGS / PGS / PGS", +"Text_Language_List": "English / German / Portuguese", +"CompleteName": "tests/data/mediainfo/media_001.mkv", +"FolderName": "tests/data/mediainfo", +"FileNameExtension": "media_001.mkv", +"FileName": "media_001", +"FileExtension": "mkv", +"Format": "Matroska", +"Format_String": "Matroska", +"Format_Url": "https://matroska.org/downloads/windows.html", +"Format_Extensions": "mkv mk3d mka mks", +"Format_Commercial": "Matroska", +"Format_Version": "4", +"FileSize": "28071815971", +"FileSize_String": "26.1 GiB", +"FileSize_String1": "26 GiB", +"FileSize_String2": "26 GiB", +"FileSize_String3": "26.1 GiB", +"FileSize_String4": "26.14 GiB", +"Duration": "3261.384", +"Duration_String": "54 min 21 s", +"Duration_String1": "54 min 21 s 384 ms", +"Duration_String2": "54 min 21 s", +"Duration_String3": "00:54:21.384", +"Duration_String4": "00:54:20;05", +"Duration_String5": "00:54:21.384 (00:54:20;05)", +"OverallBitRate_Mode": "VBR", +"OverallBitRate_Mode_String": "Variable", +"OverallBitRate": "68858659", +"OverallBitRate_String": "68.9 Mb/s", +"FrameRate": "23.976", +"FrameRate_String": "23.976 FPS", +"FrameCount": "78147", +"IsStreamable": "Yes", +"Title": "Media 001", +"Movie": "Media 001", +"Encoded_Date": "UTC 2020-11-20 14:48:06", +"File_Modified_Date": "UTC 2021-03-20 18:06:52", +"File_Modified_Date_Local": "2021-03-20 19:06:52", +"Encoded_Application": "mkvmerge v51.0.0 ('I Wish') 64-bit", +"Encoded_Application_String": "mkvmerge v51.0.0 ('I Wish') 64-bit", +"Encoded_Library": "libebml v1.4.0 + libmatroska v1.6.2", +"Encoded_Library_String": "libebml v1.4.0 + libmatroska v1.6.2" +}, +{ +"@type": "Video", +"Count": "378", +"StreamCount": "1", +"StreamKind": "Video", +"StreamKind_String": "Video", +"StreamKindID": "0", +"StreamOrder": "0", +"ID": "1", +"ID_String": "1", +"OriginalSourceMedium_ID": "4113", +"OriginalSourceMedium_ID_String": "4113 (0x1011)", +"UniqueID": "1", +"Format": "HEVC", +"Format_String": "HEVC", +"Format_Info": "High Efficiency Video Coding", +"Format_Url": "http://www.itu.int", +"Format_Commercial": "HEVC", +"Format_Profile": "Main 10", +"Format_Level": "5.1", +"Format_Tier": "High", +"HDR_Format": "Dolby Vision / SMPTE ST 2086", +"HDR_Format_String": "Dolby Vision, Version 1.0, dvhe.07.06, BL+EL+RPU, Blu-ray compatible / SMPTE ST 2086, HDR10 compatible", +"HDR_Format_Commercial": "Blu-ray / HDR10", +"HDR_Format_Version": "1.0 / ", +"HDR_Format_Profile": "dvhe.07 / ", +"HDR_Format_Level": "06 / ", +"HDR_Format_Settings": "BL+EL+RPU / ", +"HDR_Format_Compatibility": "Blu-ray / HDR10", +"InternetMediaType": "video/H265", +"CodecID": "V_MPEGH/ISO/HEVC", +"Duration": "3259.381000000", +"Duration_String": "54 min 19 s", +"Duration_String1": "54 min 19 s 381 ms", +"Duration_String2": "54 min 19 s", +"Duration_String3": "00:54:19.381", +"Duration_String4": "00:54:20;05", +"Duration_String5": "00:54:19.381 (00:54:20;05)", +"BitRate": "62232772", +"BitRate_String": "62.2 Mb/s", +"Width": "3840", +"Width_String": "3 840 pixels", +"Height": "2160", +"Height_String": "2 160 pixels", +"Sampled_Width": "3840", +"Sampled_Height": "2160", +"PixelAspectRatio": "1.000", +"DisplayAspectRatio": "1.778", +"DisplayAspectRatio_String": "16:9", +"FrameRate_Mode": "CFR", +"FrameRate_Mode_String": "Constant", +"FrameRate": "23.976", +"FrameRate_String": "23.976 (24000/1001) FPS", +"FrameRate_Num": "24000", +"FrameRate_Den": "1001", +"FrameCount": "78147", +"ColorSpace": "YUV", +"ChromaSubsampling": "4:2:0", +"ChromaSubsampling_String": "4:2:0 (Type 2)", +"ChromaSubsampling_Position": "Type 2", +"BitDepth": "10", +"BitDepth_String": "10 bits", +"BitsPixel_Frame": "0.313", +"Delay": "0.000", +"Delay_String3": "00:00:00.000", +"Delay_Source": "Container", +"Delay_Source_String": "Container", +"StreamSize": "25355039372", +"StreamSize_String": "23.6 GiB (90%)", +"StreamSize_String1": "24 GiB", +"StreamSize_String2": "24 GiB", +"StreamSize_String3": "23.6 GiB", +"StreamSize_String4": "23.61 GiB", +"StreamSize_String5": "23.6 GiB (90%)", +"StreamSize_Proportion": "0.90322", +"Language": "en", +"Language_String": "English", +"Language_String1": "English", +"Language_String2": "en", +"Language_String3": "eng", +"Language_String4": "en", +"Default": "No", +"Default_String": "No", +"Forced": "No", +"Forced_String": "No", +"colour_description_present": "Yes", +"colour_description_present_Source": "Stream", +"colour_range": "Limited", +"colour_range_Source": "Stream", +"colour_primaries": "BT.2020", +"colour_primaries_Source": "Stream", +"transfer_characteristics": "PQ", +"transfer_characteristics_Source": "Stream", +"matrix_coefficients": "BT.2020 non-constant", +"matrix_coefficients_Source": "Stream", +"MasteringDisplay_ColorPrimaries": "BT.2020", +"MasteringDisplay_ColorPrimaries_Source": "Stream", +"MasteringDisplay_Luminance": "min: 0.0020 cd/m2, max: 1000 cd/m2", +"MasteringDisplay_Luminance_Source": "Stream", +"extra": { +"OriginalSourceMedium": "Blu-ray" +} +}, +{ +"@type": "Audio", +"@typeorder": "1", +"Count": "285", +"StreamCount": "4", +"StreamKind": "Audio", +"StreamKind_String": "Audio", +"StreamKindID": "0", +"StreamKindPos": "1", +"StreamOrder": "1", +"ID": "2", +"ID_String": "2", +"OriginalSourceMedium_ID": "4353", +"OriginalSourceMedium_ID_String": "4353 (0x1101)", +"UniqueID": "2", +"Format": "MLP FBA", +"Format_String": "MLP FBA 16-ch", +"Format_Info": "Meridian Lossless Packing FBA with 16-channel presentation", +"Format_Commercial": "Dolby TrueHD with Dolby Atmos", +"Format_Commercial_IfAny": "Dolby TrueHD with Dolby Atmos", +"Format_AdditionalFeatures": "16-ch", +"CodecID": "A_TRUEHD", +"CodecID_Url": "http://www.dolby.com/consumer/technology/trueHD.html", +"Duration": "3259.381000000", +"Duration_String": "54 min 19 s", +"Duration_String1": "54 min 19 s 381 ms", +"Duration_String2": "54 min 19 s", +"Duration_String3": "00:54:19.381", +"Duration_String5": "00:54:19.381", +"BitRate_Mode": "VBR", +"BitRate_Mode_String": "Variable", +"BitRate": "4694029", +"BitRate_String": "4 694 kb/s", +"BitRate_Maximum": "7545000", +"BitRate_Maximum_String": "7 545 kb/s", +"Channels": "8", +"Channels_String": "8 channels", +"ChannelPositions": "Front: L C R, Side: L R, Back: L R, LFE", +"ChannelPositions_String2": "3/2/2.1", +"ChannelLayout": "L R C LFE Ls Rs Lb Rb", +"SamplesPerFrame": "40", +"SamplingRate": "48000", +"SamplingRate_String": "48.0 kHz", +"SamplingCount": "156450288", +"FrameRate": "1200.000", +"FrameRate_String": "1 200.000 FPS (40 SPF)", +"FrameCount": "3911257", +"Compression_Mode": "Lossless", +"Compression_Mode_String": "Lossless", +"Delay": "0.000", +"Delay_String3": "00:00:00.000", +"Delay_Source": "Container", +"Delay_Source_String": "Container", +"Video_Delay": "0.000", +"Video_Delay_String3": "00:00:00.000", +"StreamSize": "1912453744", +"StreamSize_String": "1.78 GiB (7%)", +"StreamSize_String1": "2 GiB", +"StreamSize_String2": "1.8 GiB", +"StreamSize_String3": "1.78 GiB", +"StreamSize_String4": "1.781 GiB", +"StreamSize_String5": "1.78 GiB (7%)", +"StreamSize_Proportion": "0.06813", +"Language": "en", +"Language_String": "English", +"Language_String1": "English", +"Language_String2": "en", +"Language_String3": "eng", +"Language_String4": "en", +"Default": "Yes", +"Default_String": "Yes", +"Forced": "No", +"Forced_String": "No", +"extra": { +"OriginalSourceMedium": "Blu-ray", +"NumberOfDynamicObjects": "11", +"BedChannelCount": "1", +"BedChannelCount_String": "1 channel", +"BedChannelConfiguration": "LFE" +} +}, +{ +"@type": "Audio", +"@typeorder": "2", +"Count": "311", +"StreamCount": "4", +"StreamKind": "Audio", +"StreamKind_String": "Audio", +"StreamKindID": "1", +"StreamKindPos": "2", +"StreamOrder": "2", +"ID": "3", +"ID_String": "3", +"OriginalSourceMedium_ID": "4353", +"OriginalSourceMedium_ID_String": "4353 (0x1101)", +"UniqueID": "3", +"Format": "AC-3", +"Format_String": "AC-3", +"Format_Info": "Audio Coding 3", +"Format_Url": "https://en.wikipedia.org/wiki/AC3", +"Format_Commercial": "Dolby Digital", +"Format_Commercial_IfAny": "Dolby Digital", +"Format_Settings_Endianness": "Big", +"CodecID": "A_AC3", +"Duration": "3259.392000000", +"Duration_String": "54 min 19 s", +"Duration_String1": "54 min 19 s 392 ms", +"Duration_String2": "54 min 19 s", +"Duration_String3": "00:54:19.392", +"Duration_String4": "00:54:45:21", +"Duration_String5": "00:54:19.392 (00:54:45:21)", +"BitRate_Mode": "CBR", +"BitRate_Mode_String": "Constant", +"BitRate": "640000", +"BitRate_String": "640 kb/s", +"Channels": "6", +"Channels_String": "6 channels", +"ChannelPositions": "Front: L C R, Side: L R, LFE", +"ChannelPositions_String2": "3/2/0.1", +"ChannelLayout": "L R C LFE Ls Rs", +"SamplesPerFrame": "1536", +"SamplingRate": "48000", +"SamplingRate_String": "48.0 kHz", +"SamplingCount": "156450816", +"FrameRate": "31.250", +"FrameRate_String": "31.250 FPS (1536 SPF)", +"FrameCount": "101856", +"Compression_Mode": "Lossy", +"Compression_Mode_String": "Lossy", +"Delay": "0.000", +"Delay_String3": "00:00:00.000", +"Delay_Source": "Container", +"Delay_Source_String": "Container", +"Video_Delay": "0.000", +"Video_Delay_String3": "00:00:00.000", +"StreamSize": "260751360", +"StreamSize_String": "249 MiB (1%)", +"StreamSize_String1": "249 MiB", +"StreamSize_String2": "249 MiB", +"StreamSize_String3": "249 MiB", +"StreamSize_String4": "248.7 MiB", +"StreamSize_String5": "249 MiB (1%)", +"StreamSize_Proportion": "0.00929", +"Language": "en", +"Language_String": "English", +"Language_String1": "English", +"Language_String2": "en", +"Language_String3": "eng", +"Language_String4": "en", +"ServiceKind": "CM", +"ServiceKind_String": "Complete Main", +"Default": "No", +"Default_String": "No", +"Forced": "No", +"Forced_String": "No", +"extra": { +"OriginalSourceMedium": "Blu-ray", +"bsid": "6", +"dialnorm": "-31", +"dialnorm_String": "-31 dB", +"compr": "0.53", +"compr_String": "0.53 dB", +"dynrng": "0.27", +"dynrng_String": "0.27 dB", +"acmod": "7", +"lfeon": "1", +"dialnorm_Average": "-31", +"dialnorm_Average_String": "-31 dB", +"dialnorm_Minimum": "-31", +"dialnorm_Minimum_String": "-31 dB", +"dialnorm_Maximum": "-31", +"dialnorm_Maximum_String": "-31 dB", +"dialnorm_Count": "102", +"compr_Average": "2.47", +"compr_Average_String": "2.47 dB", +"compr_Minimum": "-0.86", +"compr_Minimum_String": "-0.86 dB", +"compr_Maximum": "3.88", +"compr_Maximum_String": "3.88 dB", +"compr_Count": "101", +"dynrng_Average": "1.89", +"dynrng_Average_String": "1.89 dB", +"dynrng_Minimum": "-0.71", +"dynrng_Minimum_String": "-0.71 dB", +"dynrng_Maximum": "3.52", +"dynrng_Maximum_String": "3.52 dB", +"dynrng_Count": "101" +} +}, +{ +"@type": "Audio", +"@typeorder": "3", +"Count": "309", +"StreamCount": "4", +"StreamKind": "Audio", +"StreamKind_String": "Audio", +"StreamKindID": "2", +"StreamKindPos": "3", +"StreamOrder": "3", +"ID": "4", +"ID_String": "4", +"OriginalSourceMedium_ID": "4357", +"OriginalSourceMedium_ID_String": "4357 (0x1105)", +"UniqueID": "4", +"Format": "AC-3", +"Format_String": "AC-3", +"Format_Info": "Audio Coding 3", +"Format_Url": "https://en.wikipedia.org/wiki/AC3", +"Format_Commercial": "Dolby Digital", +"Format_Commercial_IfAny": "Dolby Digital", +"Format_Settings_Endianness": "Big", +"CodecID": "A_AC3", +"Duration": "3258.272000000", +"Duration_String": "54 min 18 s", +"Duration_String1": "54 min 18 s 272 ms", +"Duration_String2": "54 min 18 s", +"Duration_String3": "00:54:18.272", +"Duration_String4": "00:54:44:17", +"Duration_String5": "00:54:18.272 (00:54:44:17)", +"BitRate_Mode": "CBR", +"BitRate_Mode_String": "Constant", +"BitRate": "448000", +"BitRate_String": "448 kb/s", +"Channels": "6", +"Channels_String": "6 channels", +"ChannelPositions": "Front: L C R, Side: L R, LFE", +"ChannelPositions_String2": "3/2/0.1", +"ChannelLayout": "L R C LFE Ls Rs", +"SamplesPerFrame": "1536", +"SamplingRate": "48000", +"SamplingRate_String": "48.0 kHz", +"SamplingCount": "156397056", +"FrameRate": "31.250", +"FrameRate_String": "31.250 FPS (1536 SPF)", +"FrameCount": "101821", +"Compression_Mode": "Lossy", +"Compression_Mode_String": "Lossy", +"Delay": "2.002", +"Delay_String": "2 s 2 ms", +"Delay_String1": "2 s 2 ms", +"Delay_String2": "2 s 2 ms", +"Delay_String3": "00:00:02.002", +"Delay_Source": "Container", +"Delay_Source_String": "Container", +"Video_Delay": "2.002", +"Video_Delay_String": "2 s 2 ms", +"Video_Delay_String1": "2 s 2 ms", +"Video_Delay_String2": "2 s 2 ms", +"Video_Delay_String3": "00:00:02.002", +"StreamSize": "182463232", +"StreamSize_String": "174 MiB (1%)", +"StreamSize_String1": "174 MiB", +"StreamSize_String2": "174 MiB", +"StreamSize_String3": "174 MiB", +"StreamSize_String4": "174.0 MiB", +"StreamSize_String5": "174 MiB (1%)", +"StreamSize_Proportion": "0.00650", +"Language": "de", +"Language_String": "German", +"Language_String1": "German", +"Language_String2": "de", +"Language_String3": "deu", +"Language_String4": "de", +"ServiceKind": "CM", +"ServiceKind_String": "Complete Main", +"Default": "No", +"Default_String": "No", +"Forced": "No", +"Forced_String": "No", +"extra": { +"OriginalSourceMedium": "Blu-ray", +"bsid": "6", +"dialnorm": "-31", +"dialnorm_String": "-31 dB", +"compr": "-0.28", +"compr_String": "-0.28 dB", +"acmod": "7", +"lfeon": "1", +"dialnorm_Average": "-31", +"dialnorm_Average_String": "-31 dB", +"dialnorm_Minimum": "-31", +"dialnorm_Minimum_String": "-31 dB", +"dialnorm_Maximum": "-31", +"dialnorm_Maximum_String": "-31 dB", +"dialnorm_Count": "40", +"compr_Average": "0.72", +"compr_Average_String": "0.72 dB", +"compr_Minimum": "-2.87", +"compr_Minimum_String": "-2.87 dB", +"compr_Maximum": "1.49", +"compr_Maximum_String": "1.49 dB", +"compr_Count": "34", +"dynrng_Average": "0.40", +"dynrng_Average_String": "0.40 dB", +"dynrng_Minimum": "-2.87", +"dynrng_Minimum_String": "-2.87 dB", +"dynrng_Maximum": "1.26", +"dynrng_Maximum_String": "1.26 dB", +"dynrng_Count": "39" +} +}, +{ +"@type": "Audio", +"@typeorder": "4", +"Count": "281", +"StreamCount": "4", +"StreamKind": "Audio", +"StreamKind_String": "Audio", +"StreamKindID": "3", +"StreamKindPos": "4", +"StreamOrder": "4", +"ID": "5", +"ID_String": "5", +"OriginalSourceMedium_ID": "4359", +"OriginalSourceMedium_ID_String": "4359 (0x1107)", +"UniqueID": "5", +"Format": "DTS", +"Format_String": "DTS", +"Format_Info": "Digital Theater Systems", +"Format_Url": "https://en.wikipedia.org/wiki/DTS_(sound_system)", +"Format_Commercial": "DTS", +"Format_Settings_Mode": "16", +"Format_Settings_Endianness": "Big", +"CodecID": "A_DTS", +"Duration": "3259.382000000", +"Duration_String": "54 min 19 s", +"Duration_String1": "54 min 19 s 382 ms", +"Duration_String2": "54 min 19 s", +"Duration_String3": "00:54:19.382", +"Duration_String4": "00:54:10:67", +"Duration_String5": "00:54:19.382 (00:54:10:67)", +"BitRate_Mode": "CBR", +"BitRate_Mode_String": "Constant", +"BitRate": "768000", +"BitRate_String": "768 kb/s", +"Channels": "2", +"Channels_String": "2 channels", +"ChannelPositions": "Front: L R", +"ChannelPositions_String2": "2/0/0.0", +"ChannelLayout": "Lt Rt", +"SamplesPerFrame": "512", +"SamplingRate": "48000", +"SamplingRate_String": "48.0 kHz", +"SamplingCount": "156450336", +"FrameRate": "93.750", +"FrameRate_String": "93.750 FPS (512 SPF)", +"FrameCount": "305567", +"BitDepth": "24", +"BitDepth_String": "24 bits", +"Compression_Mode": "Lossy", +"Compression_Mode_String": "Lossy", +"Delay": "2.002", +"Delay_String": "2 s 2 ms", +"Delay_String1": "2 s 2 ms", +"Delay_String2": "2 s 2 ms", +"Delay_String3": "00:00:02.002", +"Delay_Source": "Container", +"Delay_Source_String": "Container", +"Video_Delay": "2.002", +"Video_Delay_String": "2 s 2 ms", +"Video_Delay_String1": "2 s 2 ms", +"Video_Delay_String2": "2 s 2 ms", +"Video_Delay_String3": "00:00:02.002", +"StreamSize": "312900608", +"StreamSize_String": "298 MiB (1%)", +"StreamSize_String1": "298 MiB", +"StreamSize_String2": "298 MiB", +"StreamSize_String3": "298 MiB", +"StreamSize_String4": "298.4 MiB", +"StreamSize_String5": "298 MiB (1%)", +"StreamSize_Proportion": "0.01115", +"Language": "pt", +"Language_String": "Portuguese", +"Language_String1": "Portuguese", +"Language_String2": "pt", +"Language_String3": "por", +"Language_String4": "pt", +"Default": "No", +"Default_String": "No", +"Forced": "No", +"Forced_String": "No", +"extra": { +"OriginalSourceMedium": "Blu-ray" +} +}, +{ +"@type": "Text", +"@typeorder": "1", +"Count": "239", +"StreamCount": "3", +"StreamKind": "Text", +"StreamKind_String": "Text", +"StreamKindID": "0", +"StreamKindPos": "1", +"StreamOrder": "5", +"ID": "6", +"ID_String": "6", +"OriginalSourceMedium_ID": "4768", +"OriginalSourceMedium_ID_String": "4768 (0x12A0)", +"UniqueID": "6", +"Format": "PGS", +"Format_String": "PGS", +"Format_Commercial": "PGS", +"MuxingMode": "zlib", +"CodecID": "S_HDMV/PGS", +"CodecID_Info": "Picture based subtitle format used on BDs/HD-DVDs", +"Duration": "3072.256000000", +"Duration_String": "51 min 12 s", +"Duration_String1": "51 min 12 s 256 ms", +"Duration_String2": "51 min 12 s", +"Duration_String3": "00:51:12.256", +"Duration_String4": "00:26:10:00", +"Duration_String5": "00:51:12.256 (00:26:10:00)", +"BitRate": "58571", +"BitRate_String": "58.6 kb/s", +"FrameRate": "0.511", +"FrameRate_String": "0.511 FPS", +"FrameCount": "1570", +"ElementCount": "1570", +"StreamSize": "22493302", +"StreamSize_String": "21.5 MiB (0%)", +"StreamSize_String1": "21 MiB", +"StreamSize_String2": "21 MiB", +"StreamSize_String3": "21.5 MiB", +"StreamSize_String4": "21.45 MiB", +"StreamSize_String5": "21.5 MiB (0%)", +"StreamSize_Proportion": "0.00080", +"Language": "en", +"Language_String": "English", +"Language_String1": "English", +"Language_String2": "en", +"Language_String3": "eng", +"Language_String4": "en", +"Default": "No", +"Default_String": "No", +"Forced": "No", +"Forced_String": "No", +"extra": { +"OriginalSourceMedium": "Blu-ray" +} +}, +{ +"@type": "Text", +"@typeorder": "2", +"Count": "239", +"StreamCount": "3", +"StreamKind": "Text", +"StreamKind_String": "Text", +"StreamKindID": "1", +"StreamKindPos": "2", +"StreamOrder": "6", +"ID": "7", +"ID_String": "7", +"OriginalSourceMedium_ID": "4772", +"OriginalSourceMedium_ID_String": "4772 (0x12A4)", +"UniqueID": "11", +"Format": "PGS", +"Format_String": "PGS", +"Format_Commercial": "PGS", +"MuxingMode": "zlib", +"CodecID": "S_HDMV/PGS", +"CodecID_Info": "Picture based subtitle format used on BDs/HD-DVDs", +"Duration": "3034.427000000", +"Duration_String": "50 min 34 s", +"Duration_String1": "50 min 34 s 427 ms", +"Duration_String2": "50 min 34 s", +"Duration_String3": "00:50:34.427", +"Duration_String5": "00:50:34.427", +"BitRate": "67345", +"BitRate_String": "67.3 kb/s", +"FrameRate": "0.488", +"FrameRate_String": "0.488 FPS", +"FrameCount": "1480", +"ElementCount": "1480", +"StreamSize": "25544393", +"StreamSize_String": "24.4 MiB (0%)", +"StreamSize_String1": "24 MiB", +"StreamSize_String2": "24 MiB", +"StreamSize_String3": "24.4 MiB", +"StreamSize_String4": "24.36 MiB", +"StreamSize_String5": "24.4 MiB (0%)", +"StreamSize_Proportion": "0.00091", +"Language": "de", +"Language_String": "German", +"Language_String1": "German", +"Language_String2": "de", +"Language_String3": "deu", +"Language_String4": "de", +"Default": "No", +"Default_String": "No", +"Forced": "No", +"Forced_String": "No", +"extra": { +"OriginalSourceMedium": "Blu-ray" +} +}, +{ +"@type": "Text", +"@typeorder": "3", +"Count": "239", +"StreamCount": "3", +"StreamKind": "Text", +"StreamKind_String": "Text", +"StreamKindID": "2", +"StreamKindPos": "3", +"StreamOrder": "7", +"ID": "8", +"ID_String": "8", +"OriginalSourceMedium_ID": "4774", +"OriginalSourceMedium_ID_String": "4774 (0x12A6)", +"UniqueID": "14", +"Format": "PGS", +"Format_String": "PGS", +"Format_Commercial": "PGS", +"MuxingMode": "zlib", +"CodecID": "S_HDMV/PGS", +"CodecID_Info": "Picture based subtitle format used on BDs/HD-DVDs", +"Duration": "3034.427000000", +"Duration_String": "50 min 34 s", +"Duration_String1": "50 min 34 s 427 ms", +"Duration_String2": "50 min 34 s", +"Duration_String3": "00:50:34.427", +"Duration_String5": "00:50:34.427", +"BitRate": "57693", +"BitRate_String": "57.7 kb/s", +"FrameRate": "0.487", +"FrameRate_String": "0.487 FPS", +"FrameCount": "1478", +"ElementCount": "1478", +"StreamSize": "21883368", +"StreamSize_String": "20.9 MiB (0%)", +"StreamSize_String1": "21 MiB", +"StreamSize_String2": "21 MiB", +"StreamSize_String3": "20.9 MiB", +"StreamSize_String4": "20.87 MiB", +"StreamSize_String5": "20.9 MiB (0%)", +"StreamSize_Proportion": "0.00078", +"Language": "pt", +"Language_String": "Portuguese", +"Language_String1": "Portuguese", +"Language_String2": "pt", +"Language_String3": "por", +"Language_String4": "pt", +"Default": "No", +"Default_String": "No", +"Forced": "No", +"Forced_String": "No", +"extra": { +"OriginalSourceMedium": "Blu-ray" +} +}, +{ +"@type": "Menu", +"Count": "103", +"StreamCount": "1", +"StreamKind": "Menu", +"StreamKind_String": "Menu", +"StreamKindID": "0", +"Chapters_Pos_Begin": "94", +"Chapters_Pos_End": "103", +"extra": { +"_00_00_00_000": "en:Chapter 01", +"_00_00_07_966": "en:Chapter 02", +"_00_01_54_114": "en:Chapter 03", +"_00_06_25_676": "en:Chapter 04", +"_00_16_17_977": "en:Chapter 05", +"_00_25_52_884": "en:Chapter 06", +"_00_34_16_179": "en:Chapter 07", +"_00_41_52_343": "en:Chapter 08", +"_00_53_10_812": "en:Chapter 09" +} +} +] +} +} + diff --git a/tests/data/mediainfo/media_001.mkv.yml b/tests/data/mediainfo/media_001.mkv.yml new file mode 100644 index 0000000..6104c88 --- /dev/null +++ b/tests/data/mediainfo/media_001.mkv.yml @@ -0,0 +1,85 @@ +title: Media 001 +path: tests/data/mediainfo/media_001.mkv +duration: 3261.384 +size: 28071815971 +bit_rate: 68858659 +container: mkv +video: +- id: 1 + language: en + duration: 3259.381 + size: 25355039372 + width: 3840 + height: 2160 + scan_type: PROGRESSIVE + aspect_ratio: 1.778 + pixel_aspect_ratio: 1.0 + resolution: 2160p + frame_rate: 23.976 + bit_rate: 62232772 + bit_depth: 10 + codec: H265 + profile: MAIN10 + profile_level: 5.1 + media_type: video/H265 +audio: +- id: 2 + language: en + duration: 3259.381 + size: 1912453744 + codec: + - TRUEHD + - ATMOS + channels_count: 8 + channels: 7.1 + bit_rate: 4694029 + bit_rate_mode: VBR + sampling_rate: 48000 + compression: LOSSLESS + default: true +- id: 3 + language: en + duration: 3259.392 + size: 260751360 + codec: AC3 + channels_count: 6 + channels: 5.1 + bit_rate: 640000 + bit_rate_mode: CBR + sampling_rate: 48000 + compression: LOSSY +- id: 4 + language: de + duration: 3258.272 + size: 182463232 + codec: AC3 + channels_count: 6 + channels: 5.1 + bit_rate: 448000 + bit_rate_mode: CBR + sampling_rate: 48000 + compression: LOSSY +- id: 5 + language: pt + duration: 3259.382 + size: 312900608 + codec: DTS + channels_count: 2 + channels: 2.0 + bit_depth: 24 + bit_rate: 768000 + bit_rate_mode: CBR + sampling_rate: 48000 + compression: LOSSY +subtitle: +- id: 6 + language: en + format: PGS +- id: 7 + language: de + format: PGS +- id: 8 + language: pt + format: PGS +provider: + name: mediainfo From f3df71fdb0b9d11c85d0784632eef222ad4b472d Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 18:21:26 +0100 Subject: [PATCH 085/102] Add mkvtoolnix to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 868f0f9..1cd1591 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ addons: packages: - mediainfo - ffmpeg + - mkvtoolnix cache: directories: From 13f2a3693ed95ba79dd5c3ad6daf8ef9207e730c Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 18:31:35 +0100 Subject: [PATCH 086/102] Ignore 1ms difference in duration tests --- tests/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index 1977c40..3e92eb2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -42,6 +42,9 @@ }) +one_ms = timedelta(milliseconds=1) + + def normalize_path(path: str): return os.fspath(pathlib.Path(path)) @@ -281,10 +284,17 @@ def check_equals(expected, actual, different, options, prefix=''): check_mapping_equals(expected, actual, different=different, options=options, prefix=prefix) elif is_iterable(expected): check_sequence_equals(expected, actual, different=different, options=options, prefix=prefix) + elif isinstance(expected, timedelta): + check_timedelta_equals(expected, actual, different=different, prefix=prefix) elif to_string(options['profile'], expected) != to_string(options['profile'], actual): different.append((prefix, expected, actual)) +def check_timedelta_equals(expected, actual, different, prefix=''): + if not isinstance(actual, timedelta) or not (expected - one_ms) <= actual <= (expected + one_ms): + different.append((prefix, expected, actual)) + + def check_sequence_equals(expected, actual, different, options, prefix=''): if not is_iterable(actual) or len(expected) != len(actual): different.append((prefix, expected, actual)) From f053b72ec154577d1e64c2ce3223517d88f43d12 Mon Sep 17 00:00:00 2001 From: Rato Date: Sat, 27 Mar 2021 19:00:51 +0100 Subject: [PATCH 087/102] Add basic HDR detection: HDR10 and Dolby Vision --- knowit/defaults.yml | 13 +++++++++++++ knowit/properties/__init__.py | 1 + knowit/properties/video/__init__.py | 1 + knowit/properties/video/hdr.py | 6 ++++++ knowit/providers/mediainfo.py | 3 +++ tests/data/mediainfo/media_001.mkv.yml | 3 +++ tests/test_properties.yml | 6 ++++++ 7 files changed, 33 insertions(+) create mode 100644 knowit/properties/video/hdr.py diff --git a/knowit/defaults.yml b/knowit/defaults.yml index e218323..af6b79c 100644 --- a/knowit/defaults.yml +++ b/knowit/defaults.yml @@ -200,6 +200,12 @@ knowledge: HIGH: - HIGH + VideoHdrFormat: + DV: + - DOLBY VISION + HDR10: + - SMPTE ST 2086 + ScanType: PROGRESSIVE: - PROGRESSIVE @@ -505,6 +511,13 @@ profiles: HIGH: default: High + VideoHdrFormat: + DV: + default: Dolby Vision + HDR10: + default: HDR10 + technical: HDR10 - SMPTE ST 2086 + ScanType: PROGRESSIVE: default: Progressive diff --git a/knowit/properties/__init__.py b/knowit/properties/__init__.py index 13bd9ba..e7da108 100644 --- a/knowit/properties/__init__.py +++ b/knowit/properties/__init__.py @@ -19,6 +19,7 @@ VideoCodec, VideoDimensions, VideoEncoder, + VideoHdrFormat, VideoProfile, VideoProfileLevel, VideoProfileTier, diff --git a/knowit/properties/video/__init__.py b/knowit/properties/video/__init__.py index efcf5b4..889ec93 100644 --- a/knowit/properties/video/__init__.py +++ b/knowit/properties/video/__init__.py @@ -2,6 +2,7 @@ from knowit.properties.video.codec import VideoCodec from knowit.properties.video.dimensions import VideoDimensions from knowit.properties.video.encoder import VideoEncoder +from knowit.properties.video.hdr import VideoHdrFormat from knowit.properties.video.profile import VideoProfile from knowit.properties.video.profile import VideoProfileLevel from knowit.properties.video.profile import VideoProfileTier diff --git a/knowit/properties/video/hdr.py b/knowit/properties/video/hdr.py new file mode 100644 index 0000000..1bbe379 --- /dev/null +++ b/knowit/properties/video/hdr.py @@ -0,0 +1,6 @@ + +from knowit.property import Configurable + + +class VideoHdrFormat(Configurable): + """Video HDR Format property.""" diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index ef4e90c..753b299 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -24,6 +24,7 @@ SubtitleFormat, VideoCodec, VideoEncoder, + VideoHdrFormat, VideoProfile, VideoProfileTier, YesNo, @@ -212,6 +213,8 @@ def __init__(self, config, suggested_path): 'profile_level': Property('Format_Level', description='video codec profile level'), 'profile_tier': VideoProfileTier(config, 'Format_Tier', description='video codec profile tier'), 'encoder': VideoEncoder(config, 'Encoded_Library_Name', description='video encoder'), + 'hdr_format': MultiValue(VideoHdrFormat(config, 'HDR_Format', description='video hdr format'), + delimiter=' / '), 'media_type': Property('InternetMediaType', description='video media type'), 'forced': YesNo('Forced', hide_value=False, description='video track forced'), 'default': YesNo('Default', hide_value=False, description='video track default'), diff --git a/tests/data/mediainfo/media_001.mkv.yml b/tests/data/mediainfo/media_001.mkv.yml index 6104c88..baceafc 100644 --- a/tests/data/mediainfo/media_001.mkv.yml +++ b/tests/data/mediainfo/media_001.mkv.yml @@ -21,6 +21,9 @@ video: codec: H265 profile: MAIN10 profile_level: 5.1 + hdr_format: + - DV + - HDR10 media_type: video/H265 audio: - id: 2 diff --git a/tests/test_properties.yml b/tests/test_properties.yml index 1dedf4e..260bdf3 100644 --- a/tests/test_properties.yml +++ b/tests/test_properties.yml @@ -153,6 +153,12 @@ VideoProfileTier: High: - Main 10@L4@High +VideoHdrFormat: + Dolby Vision: + - Dolby Vision + HDR10: + - SMPTE ST 2086 + ScanType: Progressive: - Progressive From d28b4e41e6acc703e4391b8d286dac6f27c7a307 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 00:33:23 -0400 Subject: [PATCH 088/102] Merge rule and property into core --- knowit/core.py | 182 +++++++++++++++++++++++ knowit/properties/audio/bitratemode.py | 2 +- knowit/properties/audio/channels.py | 2 +- knowit/properties/audio/codec.py | 2 +- knowit/properties/audio/compression.py | 2 +- knowit/properties/audio/profile.py | 2 +- knowit/properties/basic.py | 2 +- knowit/properties/duration.py | 2 +- knowit/properties/language.py | 2 +- knowit/properties/quantity.py | 2 +- knowit/properties/subtitle/format.py | 2 +- knowit/properties/video/codec.py | 2 +- knowit/properties/video/dimensions.py | 2 +- knowit/properties/video/encoder.py | 2 +- knowit/properties/video/hdr.py | 2 +- knowit/properties/video/profile.py | 2 +- knowit/properties/video/ratio.py | 2 +- knowit/properties/video/scantype.py | 2 +- knowit/properties/yesno.py | 2 +- knowit/property.py | 179 ---------------------- knowit/provider.py | 3 +- knowit/providers/enzyme.py | 2 +- knowit/providers/ffmpeg.py | 4 +- knowit/providers/mediainfo.py | 10 +- knowit/providers/mkvmerge.py | 2 +- knowit/rule.py | 19 --- knowit/rules/audio/atmos.py | 2 +- knowit/rules/audio/channels.py | 2 +- knowit/rules/audio/dtshd.py | 2 +- knowit/rules/language.py | 2 +- knowit/rules/subtitle/closedcaption.py | 2 +- knowit/rules/subtitle/hearingimpaired.py | 2 +- knowit/rules/video/resolution.py | 2 +- 33 files changed, 216 insertions(+), 235 deletions(-) delete mode 100644 knowit/property.py delete mode 100644 knowit/rule.py diff --git a/knowit/core.py b/knowit/core.py index a81ee03..3de7d93 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -6,6 +6,12 @@ T = typing.TypeVar('T') +_visible_chars_table = dict.fromkeys(range(32)) + + +def _is_unknown(value: typing.Any) -> bool: + return isinstance(value, str) and (not value or value.lower() == 'unknown') + class Reportable(typing.Generic[T]): """Reportable abstract class.""" @@ -36,3 +42,179 @@ def report(self, value: typing.Union[str, T], context: typing.MutableMapping) -> if value not in report_map: report_map[value] = context['path'] logger.info('Invalid %s: %r', self.description, value) + + +class Property(Reportable[T]): + """Property class.""" + + def __init__( + self, + *args: str, + default: typing.Optional[T] = None, + private: bool = False, + description: typing.Optional[str] = None, + delimiter: str = ' / ', + **kwargs, + ): + """Init method.""" + super().__init__(*args, description=description, **kwargs) + self.default = default + self.private = private + # Used to detect duplicated values. e.g.: en / en or High@L4.0 / High@L4.0 or Progressive / Progressive + self.delimiter = delimiter + + def extract_value( + self, + track: typing.Mapping, + context: typing.MutableMapping, + ) -> typing.Optional[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) + if value is None: + if self.default is None: + continue + + value = self.default + + if isinstance(value, bytes): + value = value.decode() + + if isinstance(value, str): + value = value.translate(_visible_chars_table).strip() + if _is_unknown(value): + continue + value = self._deduplicate(value) + + result = self.handle(value, context) + if result is not None and not _is_unknown(result): + return result + + return None + + @classmethod + def _deduplicate(cls, value: str) -> str: + values = value.split(' / ') + if len(values) == 2 and values[0] == values[1]: + return values[0] + return value + + def handle(self, value: T, context: typing.MutableMapping) -> typing.Optional[T]: + """Return the value without any modification.""" + return value + + +class Configurable(Property[T]): + """Configurable property where values are in a config mapping.""" + + def __init__(self, config: typing.Mapping[str, typing.Mapping], *args: str, + config_key: typing.Optional[str] = None, **kwargs): + """Init method.""" + super().__init__(*args, **kwargs) + self.mapping = getattr(config, config_key or self.__class__.__name__) if config else {} + + @classmethod + def _extract_key(cls, value: str) -> typing.Union[str, bool]: + return value.upper() + + @classmethod + def _extract_fallback_key(cls, value: str, key: str) -> typing.Optional[T]: + return None + + def _lookup( + self, + key: str, + context: typing.MutableMapping, + ) -> typing.Union[T, None, bool]: + result = self.mapping.get(key) + if result is not None: + result = getattr(result, context.get('profile') or 'default') + return result if result != '__ignored__' else False + return None + + def handle(self, value, context): + """Return Variable or Constant.""" + key = self._extract_key(value) + if key is False: + return + + result = self._lookup(key, context) + if result is False: + return + + while not result and key: + key = self._extract_fallback_key(value, key) + result = self._lookup(key, context) + if result is False: + return + + if not result: + self.report(value, context) + + return result + + +class MultiValue(Property): + """Property with multiple values.""" + + def __init__(self, prop: typing.Optional[Property[typing.Any]] = None, delimiter='/', single=False, + handler=None, name=None, **kwargs): + """Init method.""" + super().__init__(*(prop.names if prop else (name,)), **kwargs) + self.prop = prop + self.delimiter = delimiter + self.single = single + self.handler = handler + + def handle( + self, + value: str, + context: typing.Mapping, + ) -> typing.Union[T, typing.List[T]]: + """Handle properties with multiple values.""" + call = self.handler or self.prop.handle + result = call(value, context) + if result is not None: + return result + + if isinstance(value, list): + if len(value) == 1: + values = self._split(value[0], self.delimiter) + else: + values = value + else: + values = self._split(value, self.delimiter) + + if values is None: + return call(values, context) + if len(values) > 1 and not self.single: + results = [call(item, context) if not _is_unknown(item) else None for item in values] + results = [r for r in results if r is not None] + if results: + return results + return call(values[0], context) + + @classmethod + def _split( + cls, + value: typing.Optional[T], + delimiter: str = '/', + ) -> typing.Optional[typing.List[str]]: + if value is None: + return None + + return [x.strip() for x in str(value).split(delimiter)] + + +class Rule(Reportable[T]): + """Rule abstract class.""" + + def __init__(self, name: str, override=False, **kwargs): + """Initialize the object.""" + super().__init__(name, **kwargs) + self.override = override + + def execute(self, props, pv_props, context: typing.Mapping): + """How to execute a rule.""" + raise NotImplementedError diff --git a/knowit/properties/audio/bitratemode.py b/knowit/properties/audio/bitratemode.py index 838a996..7d978d4 100644 --- a/knowit/properties/audio/bitratemode.py +++ b/knowit/properties/audio/bitratemode.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class BitRateMode(Configurable[str]): diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py index ac97100..e9d22b6 100644 --- a/knowit/properties/audio/channels.py +++ b/knowit/properties/audio/channels.py @@ -1,6 +1,6 @@ import typing -from knowit.property import Property +from knowit.core import Property class AudioChannels(Property[int]): diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py index 6628c09..fe037c7 100644 --- a/knowit/properties/audio/codec.py +++ b/knowit/properties/audio/codec.py @@ -1,6 +1,6 @@ import typing -from knowit.property import Configurable +from knowit.core import Configurable class AudioCodec(Configurable[str]): diff --git a/knowit/properties/audio/compression.py b/knowit/properties/audio/compression.py index 30253a9..233f07b 100644 --- a/knowit/properties/audio/compression.py +++ b/knowit/properties/audio/compression.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class AudioCompression(Configurable[str]): diff --git a/knowit/properties/audio/profile.py b/knowit/properties/audio/profile.py index 4dea05e..df207b3 100644 --- a/knowit/properties/audio/profile.py +++ b/knowit/properties/audio/profile.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class AudioProfile(Configurable[str]): diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py index caf3e91..4bff95d 100644 --- a/knowit/properties/basic.py +++ b/knowit/properties/basic.py @@ -1,6 +1,6 @@ import typing -from knowit.property import Property +from knowit.core import Property T = typing.TypeVar('T') diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py index a39b83d..0e53338 100644 --- a/knowit/properties/duration.py +++ b/knowit/properties/duration.py @@ -3,7 +3,7 @@ from datetime import timedelta from decimal import Decimal, InvalidOperation -from knowit.property import Property +from knowit.core import Property class Duration(Property[timedelta]): diff --git a/knowit/properties/language.py b/knowit/properties/language.py index 06f3763..619c026 100644 --- a/knowit/properties/language.py +++ b/knowit/properties/language.py @@ -2,7 +2,7 @@ import babelfish -from knowit.property import Property +from knowit.core import Property class Language(Property[babelfish.Language]): diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py index 37885d4..af62a66 100644 --- a/knowit/properties/quantity.py +++ b/knowit/properties/quantity.py @@ -1,6 +1,6 @@ from decimal import Decimal -from knowit.property import Property +from knowit.core import Property from knowit.utils import round_decimal diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle/format.py index e7f8058..67f2733 100644 --- a/knowit/properties/subtitle/format.py +++ b/knowit/properties/subtitle/format.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class SubtitleFormat(Configurable[str]): diff --git a/knowit/properties/video/codec.py b/knowit/properties/video/codec.py index f4dabe3..1b1f6a5 100644 --- a/knowit/properties/video/codec.py +++ b/knowit/properties/video/codec.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class VideoCodec(Configurable[str]): diff --git a/knowit/properties/video/dimensions.py b/knowit/properties/video/dimensions.py index 0a1b595..c4c933e 100644 --- a/knowit/properties/video/dimensions.py +++ b/knowit/properties/video/dimensions.py @@ -2,7 +2,7 @@ import re import typing -from knowit.property import Property +from knowit.core import Property class VideoDimensions(Property[int]): diff --git a/knowit/properties/video/encoder.py b/knowit/properties/video/encoder.py index 58d122a..ecb2748 100644 --- a/knowit/properties/video/encoder.py +++ b/knowit/properties/video/encoder.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class VideoEncoder(Configurable): diff --git a/knowit/properties/video/hdr.py b/knowit/properties/video/hdr.py index 1bbe379..a1f42b2 100644 --- a/knowit/properties/video/hdr.py +++ b/knowit/properties/video/hdr.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class VideoHdrFormat(Configurable): diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py index 8f02cef..ca27c51 100644 --- a/knowit/properties/video/profile.py +++ b/knowit/properties/video/profile.py @@ -1,6 +1,6 @@ import typing -from knowit.property import Configurable +from knowit.core import Configurable class VideoProfile(Configurable[str]): diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py index 605c2a4..ef5f9ea 100644 --- a/knowit/properties/video/ratio.py +++ b/knowit/properties/video/ratio.py @@ -3,7 +3,7 @@ import typing from decimal import Decimal -from knowit.property import Property +from knowit.core import Property from knowit.utils import round_decimal diff --git a/knowit/properties/video/scantype.py b/knowit/properties/video/scantype.py index 2021a3f..42b3c9f 100644 --- a/knowit/properties/video/scantype.py +++ b/knowit/properties/video/scantype.py @@ -1,5 +1,5 @@ -from knowit.property import Configurable +from knowit.core import Configurable class ScanType(Configurable[str]): diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py index 992de82..f223281 100644 --- a/knowit/properties/yesno.py +++ b/knowit/properties/yesno.py @@ -1,6 +1,6 @@ import typing -from knowit.property import Configurable +from knowit.core import Configurable class YesNo(Configurable[str]): diff --git a/knowit/property.py b/knowit/property.py deleted file mode 100644 index 734d8a7..0000000 --- a/knowit/property.py +++ /dev/null @@ -1,179 +0,0 @@ -import typing -from logging import NullHandler, getLogger - -from knowit.core import Reportable - -logger = getLogger(__name__) -logger.addHandler(NullHandler()) - -_visible_chars_table = dict.fromkeys(range(32)) - - -def _is_unknown(value: typing.Any) -> bool: - return isinstance(value, str) and (not value or value.lower() == 'unknown') - - -T = typing.TypeVar('T') - - -class Property(Reportable[T]): - """Property class.""" - - def __init__( - self, - *args: str, - default: typing.Optional[T] = None, - private: bool = False, - description: typing.Optional[str] = None, - delimiter: str = ' / ', - **kwargs, - ): - """Init method.""" - super().__init__(*args, description=description, **kwargs) - self.default = default - self.private = private - # Used to detect duplicated values. e.g.: en / en or High@L4.0 / High@L4.0 or Progressive / Progressive - self.delimiter = delimiter - - def extract_value( - self, - track: typing.Mapping, - context: typing.MutableMapping, - ) -> typing.Optional[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) - if value is None: - if self.default is None: - continue - - value = self.default - - if isinstance(value, bytes): - value = value.decode() - - if isinstance(value, str): - value = value.translate(_visible_chars_table).strip() - if _is_unknown(value): - continue - value = self._deduplicate(value) - - result = self.handle(value, context) - if result is not None and not _is_unknown(result): - return result - - return None - - @classmethod - def _deduplicate(cls, value: str) -> str: - values = value.split(' / ') - if len(values) == 2 and values[0] == values[1]: - return values[0] - return value - - def handle(self, value: T, context: typing.MutableMapping) -> typing.Optional[T]: - """Return the value without any modification.""" - return value - - -class Configurable(Property[T]): - """Configurable property where values are in a config mapping.""" - - def __init__(self, config: typing.Mapping[str, typing.Mapping], *args: str, - config_key: typing.Optional[str] = None, **kwargs): - """Init method.""" - super().__init__(*args, **kwargs) - self.mapping = getattr(config, config_key or self.__class__.__name__) if config else {} - - @classmethod - def _extract_key(cls, value: str) -> typing.Union[str, bool]: - return value.upper() - - @classmethod - def _extract_fallback_key(cls, value: str, key: str) -> typing.Optional[T]: - return None - - def _lookup( - self, - key: str, - context: typing.MutableMapping, - ) -> typing.Union[T, None, bool]: - result = self.mapping.get(key) - if result is not None: - result = getattr(result, context.get('profile') or 'default') - return result if result != '__ignored__' else False - return None - - def handle(self, value, context): - """Return Variable or Constant.""" - key = self._extract_key(value) - if key is False: - return - - result = self._lookup(key, context) - if result is False: - return - - while not result and key: - key = self._extract_fallback_key(value, key) - result = self._lookup(key, context) - if result is False: - return - - if not result: - self.report(value, context) - - return result - - -class MultiValue(Property): - """Property with multiple values.""" - - def __init__(self, prop: typing.Optional[Property[typing.Any]] = None, delimiter='/', single=False, - handler=None, name=None, **kwargs): - """Init method.""" - super().__init__(*(prop.names if prop else (name,)), **kwargs) - self.prop = prop - self.delimiter = delimiter - self.single = single - self.handler = handler - - def handle( - self, - value: str, - context: typing.Mapping, - ) -> typing.Union[T, typing.List[T]]: - """Handle properties with multiple values.""" - call = self.handler or self.prop.handle - result = call(value, context) - if result is not None: - return result - - if isinstance(value, list): - if len(value) == 1: - values = self._split(value[0], self.delimiter) - else: - values = value - else: - values = self._split(value, self.delimiter) - - if values is None: - return call(values, context) - if len(values) > 1 and not self.single: - results = [call(item, context) if not _is_unknown(item) else None for item in values] - results = [r for r in results if r is not None] - if results: - return results - return call(values[0], context) - - @classmethod - def _split( - cls, - value: typing.Optional[T], - delimiter: str = '/', - ) -> typing.Optional[typing.List[str]]: - if value is None: - return None - - return [x.strip() for x in str(value).split(delimiter)] diff --git a/knowit/provider.py b/knowit/provider.py index 56ce5d0..62be920 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -5,8 +5,7 @@ import knowit.config from knowit.properties import Quantity -from knowit.property import Property -from knowit.rule import Rule +from knowit.core import Property, Rule from knowit.units import units logger = getLogger(__name__) diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index 3ebd44f..b41700f 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -14,7 +14,7 @@ VideoCodec, YesNo, ) -from knowit.property import Property +from knowit.core import Property from knowit.provider import ( MalformedFileError, Provider, diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index a40f4b0..99b82ee 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -22,9 +22,7 @@ VideoProfileLevel, YesNo, ) -from knowit.property import ( - Property, -) +from knowit.core import Property from knowit.provider import ( MalformedFileError, Provider, diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 753b299..73e166b 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -29,15 +29,15 @@ VideoProfileTier, YesNo, ) -from ..property import ( +from knowit.core import ( MultiValue, Property, ) -from ..provider import ( +from knowit.provider import ( MalformedFileError, Provider, ) -from ..rules import ( +from knowit.rules import ( AtmosRule, AudioChannelsRule, ClosedCaptionRule, @@ -46,8 +46,8 @@ LanguageRule, ResolutionRule, ) -from ..units import units -from ..utils import ( +from knowit.units import units +from knowit.utils import ( define_candidate, detect_os, round_decimal, ) diff --git a/knowit/providers/mkvmerge.py b/knowit/providers/mkvmerge.py index a0e3b49..f7c77c1 100644 --- a/knowit/providers/mkvmerge.py +++ b/knowit/providers/mkvmerge.py @@ -16,7 +16,7 @@ VideoDimensions, YesNo, ) -from knowit.property import Property +from knowit.core import Property from knowit.provider import ( MalformedFileError, Provider, diff --git a/knowit/rule.py b/knowit/rule.py deleted file mode 100644 index ca01603..0000000 --- a/knowit/rule.py +++ /dev/null @@ -1,19 +0,0 @@ -import typing - -from knowit.core import Reportable - - -T = typing.TypeVar('T') - - -class Rule(Reportable[T]): - """Rule abstract class.""" - - def __init__(self, name: str, override=False, **kwargs): - """Initialize the object.""" - super().__init__(name, **kwargs) - self.override = override - - def execute(self, props, pv_props, context: typing.Mapping): - """How to execute a rule.""" - raise NotImplementedError diff --git a/knowit/rules/audio/atmos.py b/knowit/rules/audio/atmos.py index 5b1d794..5a32c4d 100644 --- a/knowit/rules/audio/atmos.py +++ b/knowit/rules/audio/atmos.py @@ -1,6 +1,6 @@ import typing -from knowit.rule import Rule +from knowit.core import Rule class AtmosRule(Rule): diff --git a/knowit/rules/audio/channels.py b/knowit/rules/audio/channels.py index a4dcf6d..ce876b3 100644 --- a/knowit/rules/audio/channels.py +++ b/knowit/rules/audio/channels.py @@ -1,7 +1,7 @@ from decimal import Decimal from logging import NullHandler, getLogger -from knowit.rule import Rule +from knowit.core import Rule logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/rules/audio/dtshd.py b/knowit/rules/audio/dtshd.py index b9f394f..e736f1a 100644 --- a/knowit/rules/audio/dtshd.py +++ b/knowit/rules/audio/dtshd.py @@ -1,6 +1,6 @@ import typing -from knowit.rule import Rule +from knowit.core import Rule class DtsHdRule(Rule): diff --git a/knowit/rules/language.py b/knowit/rules/language.py index 9a7355a..b492c03 100644 --- a/knowit/rules/language.py +++ b/knowit/rules/language.py @@ -4,7 +4,7 @@ import babelfish -from knowit.rule import Rule +from knowit.core import Rule logger = getLogger(__name__) logger.addHandler(NullHandler()) diff --git a/knowit/rules/subtitle/closedcaption.py b/knowit/rules/subtitle/closedcaption.py index e8aa405..dd61c49 100644 --- a/knowit/rules/subtitle/closedcaption.py +++ b/knowit/rules/subtitle/closedcaption.py @@ -1,7 +1,7 @@ import re -from knowit.rule import Rule +from knowit.core import Rule class ClosedCaptionRule(Rule): diff --git a/knowit/rules/subtitle/hearingimpaired.py b/knowit/rules/subtitle/hearingimpaired.py index c96915e..150855f 100644 --- a/knowit/rules/subtitle/hearingimpaired.py +++ b/knowit/rules/subtitle/hearingimpaired.py @@ -1,7 +1,7 @@ import re -from knowit.rule import Rule +from knowit.core import Rule class HearingImpairedRule(Rule): diff --git a/knowit/rules/video/resolution.py b/knowit/rules/video/resolution.py index 61f8272..dabde0a 100644 --- a/knowit/rules/video/resolution.py +++ b/knowit/rules/video/resolution.py @@ -1,6 +1,6 @@ from decimal import Decimal -from knowit.rule import Rule +from knowit.core import Rule class ResolutionRule(Rule): From b2c47a0e6d3d041e18402e5aa38528786ec0445a Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 00:49:09 -0400 Subject: [PATCH 089/102] Merge general properties (basic, duration, etc.) --- knowit/properties/__init__.py | 13 +-- knowit/properties/basic.py | 29 ------- knowit/properties/duration.py | 44 ----------- knowit/properties/general.py | 144 ++++++++++++++++++++++++++++++++++ knowit/properties/language.py | 27 ------- knowit/properties/quantity.py | 27 ------- knowit/properties/yesno.py | 27 ------- 7 files changed, 151 insertions(+), 160 deletions(-) delete mode 100644 knowit/properties/basic.py delete mode 100644 knowit/properties/duration.py create mode 100644 knowit/properties/general.py delete mode 100644 knowit/properties/language.py delete mode 100644 knowit/properties/quantity.py delete mode 100644 knowit/properties/yesno.py diff --git a/knowit/properties/__init__.py b/knowit/properties/__init__.py index e7da108..da0cfd9 100644 --- a/knowit/properties/__init__.py +++ b/knowit/properties/__init__.py @@ -1,4 +1,3 @@ - from knowit.properties.audio import ( AudioChannels, AudioCodec, @@ -6,10 +5,13 @@ AudioProfile, BitRateMode, ) -from knowit.properties.basic import Basic -from knowit.properties.duration import Duration -from knowit.properties.language import Language -from knowit.properties.quantity import Quantity +from knowit.properties.general import ( + Basic, + Duration, + Language, + Quantity, + YesNo, +) from knowit.properties.subtitle import ( SubtitleFormat, ) @@ -24,4 +26,3 @@ VideoProfileLevel, VideoProfileTier, ) -from knowit.properties.yesno import YesNo diff --git a/knowit/properties/basic.py b/knowit/properties/basic.py deleted file mode 100644 index 4bff95d..0000000 --- a/knowit/properties/basic.py +++ /dev/null @@ -1,29 +0,0 @@ -import typing - -from knowit.core import Property - -T = typing.TypeVar('T') - - -class Basic(Property[T]): - """Basic property to handle int, Decimal and other basic types.""" - - def __init__(self, *args: str, data_type: typing.Type, - processor: typing.Optional[typing.Callable[[T], T]] = None, - allow_fallback: bool = False, **kwargs): - """Init method.""" - super().__init__(*args, **kwargs) - self.data_type = data_type - self.processor = processor or (lambda x: x) - self.allow_fallback = allow_fallback - - def handle(self, value, context: typing.MutableMapping): - """Handle value.""" - if isinstance(value, self.data_type): - return self.processor(value) - - try: - return self.processor(self.data_type(value)) - except ValueError: - if not self.allow_fallback: - self.report(value, context) diff --git a/knowit/properties/duration.py b/knowit/properties/duration.py deleted file mode 100644 index 0e53338..0000000 --- a/knowit/properties/duration.py +++ /dev/null @@ -1,44 +0,0 @@ -import re -import typing -from datetime import timedelta -from decimal import Decimal, InvalidOperation - -from knowit.core import Property - - -class Duration(Property[timedelta]): - """Duration property.""" - - duration_re = re.compile(r'(?P\d{1,2}):' - r'(?P\d{1,2}):' - r'(?P\d{1,2})(?:\.' - r'(?P\d{3})' - r'(?P\d{3})?\d*)?') - - def __init__(self, *args: str, resolution: int or Decimal = 1, **kwargs): - """Initialize a Duration.""" - super().__init__(*args, **kwargs) - self.resolution = resolution - - def handle(self, value, context: typing.MutableMapping): - """Return duration as timedelta.""" - if isinstance(value, timedelta): - return value - elif isinstance(value, int): - return timedelta(milliseconds=int(value * self.resolution)) - try: - return timedelta(milliseconds=int(Decimal(value) * self.resolution)) - except (ValueError, InvalidOperation): - pass - - match = self.duration_re.match(value) - if not match: - self.report(value, context) - return None - - params = { - key: int(value) - for key, value in match.groupdict().items() - if value - } - return timedelta(**params) diff --git a/knowit/properties/general.py b/knowit/properties/general.py new file mode 100644 index 0000000..9841bd0 --- /dev/null +++ b/knowit/properties/general.py @@ -0,0 +1,144 @@ +import re +import typing +from datetime import timedelta +from decimal import Decimal, InvalidOperation + +import babelfish + +from knowit.core import Configurable, Property +from knowit.utils import round_decimal + +T = typing.TypeVar('T') + + +class Basic(Property[T]): + """Basic property to handle int, Decimal and other basic types.""" + + def __init__(self, *args: str, data_type: typing.Type, + processor: typing.Optional[typing.Callable[[T], T]] = None, + allow_fallback: bool = False, **kwargs): + """Init method.""" + super().__init__(*args, **kwargs) + self.data_type = data_type + self.processor = processor or (lambda x: x) + self.allow_fallback = allow_fallback + + def handle(self, value, context: typing.MutableMapping): + """Handle value.""" + if isinstance(value, self.data_type): + return self.processor(value) + + try: + return self.processor(self.data_type(value)) + except ValueError: + if not self.allow_fallback: + self.report(value, context) + + +class Duration(Property[timedelta]): + """Duration property.""" + + duration_re = re.compile(r'(?P\d{1,2}):' + r'(?P\d{1,2}):' + r'(?P\d{1,2})(?:\.' + r'(?P\d{3})' + r'(?P\d{3})?\d*)?') + + def __init__(self, *args: str, resolution: int or Decimal = 1, **kwargs): + """Initialize a Duration.""" + super().__init__(*args, **kwargs) + self.resolution = resolution + + def handle(self, value, context: typing.MutableMapping): + """Return duration as timedelta.""" + if isinstance(value, timedelta): + return value + elif isinstance(value, int): + return timedelta(milliseconds=int(value * self.resolution)) + try: + return timedelta( + milliseconds=int(Decimal(value) * self.resolution)) + except (ValueError, InvalidOperation): + pass + + match = self.duration_re.match(value) + if not match: + self.report(value, context) + return None + + params = { + key: int(value) + for key, value in match.groupdict().items() + if value + } + return timedelta(**params) + + +class Language(Property[babelfish.Language]): + """Language property.""" + + def handle(self, value, context: typing.MutableMapping): + """Handle languages.""" + try: + if len(value) == 3: + return babelfish.Language.fromalpha3b(value) + + return babelfish.Language.fromietf(value) + except (babelfish.Error, ValueError): + pass + + try: + return babelfish.Language.fromname(value) + except babelfish.Error: + pass + + self.report(value, context) + return babelfish.Language('und') + + +class Quantity(Property): + """Quantity is a property with unit.""" + + def __init__(self, *args: str, unit, data_type=int, **kwargs): + """Init method.""" + super().__init__(*args, **kwargs) + self.unit = unit + self.data_type = data_type + + def handle(self, value, context): + """Handle value with unit.""" + if not isinstance(value, self.data_type): + try: + value = self.data_type(value) + except ValueError: + self.report(value, context) + return + if isinstance(value, Decimal): + value = round_decimal(value, min_digits=1, max_digits=3) + + return value if context.get('no_units') else value * self.unit + + +class YesNo(Configurable[str]): + """Yes or No handler.""" + + yes_values = ('yes', 'true', '1') + + def __init__(self, *args: str, yes=True, no=False, hide_value=None, + config: typing.Optional[ + typing.Mapping[str, typing.Mapping]] = None, + config_key: typing.Optional[str] = None, + **kwargs): + """Init method.""" + super().__init__(config or {}, config_key=config_key, *args, **kwargs) + self.yes = yes + self.no = no + self.hide_value = hide_value + + def handle(self, value, context): + """Handle boolean values.""" + result = self.yes if str(value).lower() in self.yes_values else self.no + if result == self.hide_value: + return None + + return super().handle(result, context) if self.mapping else result diff --git a/knowit/properties/language.py b/knowit/properties/language.py deleted file mode 100644 index 619c026..0000000 --- a/knowit/properties/language.py +++ /dev/null @@ -1,27 +0,0 @@ -import typing - -import babelfish - -from knowit.core import Property - - -class Language(Property[babelfish.Language]): - """Language property.""" - - def handle(self, value, context: typing.MutableMapping): - """Handle languages.""" - try: - if len(value) == 3: - return babelfish.Language.fromalpha3b(value) - - return babelfish.Language.fromietf(value) - except (babelfish.Error, ValueError): - pass - - try: - return babelfish.Language.fromname(value) - except babelfish.Error: - pass - - self.report(value, context) - return babelfish.Language('und') diff --git a/knowit/properties/quantity.py b/knowit/properties/quantity.py deleted file mode 100644 index af62a66..0000000 --- a/knowit/properties/quantity.py +++ /dev/null @@ -1,27 +0,0 @@ -from decimal import Decimal - -from knowit.core import Property -from knowit.utils import round_decimal - - -class Quantity(Property): - """Quantity is a property with unit.""" - - def __init__(self, *args: str, unit, data_type=int, **kwargs): - """Init method.""" - super().__init__(*args, **kwargs) - self.unit = unit - self.data_type = data_type - - def handle(self, value, context): - """Handle value with unit.""" - if not isinstance(value, self.data_type): - try: - value = self.data_type(value) - except ValueError: - self.report(value, context) - return - if isinstance(value, Decimal): - value = round_decimal(value, min_digits=1, max_digits=3) - - return value if context.get('no_units') else value * self.unit diff --git a/knowit/properties/yesno.py b/knowit/properties/yesno.py deleted file mode 100644 index f223281..0000000 --- a/knowit/properties/yesno.py +++ /dev/null @@ -1,27 +0,0 @@ -import typing - -from knowit.core import Configurable - - -class YesNo(Configurable[str]): - """Yes or No handler.""" - - yes_values = ('yes', 'true', '1') - - def __init__(self, *args: str, yes=True, no=False, hide_value=None, - config: typing.Optional[typing.Mapping[str, typing.Mapping]] = None, - config_key: typing.Optional[str] = None, - **kwargs): - """Init method.""" - super().__init__(config or {}, config_key=config_key, *args, **kwargs) - self.yes = yes - self.no = no - self.hide_value = hide_value - - def handle(self, value, context): - """Handle boolean values.""" - result = self.yes if str(value).lower() in self.yes_values else self.no - if result == self.hide_value: - return None - - return super().handle(result, context) if self.mapping else result From b7fcf60ee818d2a56d9c5c2c85af7e12378b4ef7 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 00:38:11 -0400 Subject: [PATCH 090/102] Convert audio package to module --- knowit/properties/audio.py | 55 ++++++++++++++++++++++++++ knowit/properties/audio/__init__.py | 6 --- knowit/properties/audio/bitratemode.py | 6 --- knowit/properties/audio/channels.py | 23 ----------- knowit/properties/audio/codec.py | 23 ----------- knowit/properties/audio/compression.py | 6 --- knowit/properties/audio/profile.py | 6 --- 7 files changed, 55 insertions(+), 70 deletions(-) create mode 100644 knowit/properties/audio.py delete mode 100644 knowit/properties/audio/__init__.py delete mode 100644 knowit/properties/audio/bitratemode.py delete mode 100644 knowit/properties/audio/channels.py delete mode 100644 knowit/properties/audio/codec.py delete mode 100644 knowit/properties/audio/compression.py delete mode 100644 knowit/properties/audio/profile.py diff --git a/knowit/properties/audio.py b/knowit/properties/audio.py new file mode 100644 index 0000000..66c4e2d --- /dev/null +++ b/knowit/properties/audio.py @@ -0,0 +1,55 @@ +import typing +from knowit.core import Property +from knowit.core import Configurable + + +class BitRateMode(Configurable[str]): + """Bit Rate mode property.""" + + +class AudioCompression(Configurable[str]): + """Audio Compression property.""" + + +class AudioProfile(Configurable[str]): + """Audio profile property.""" + + +class AudioChannels(Property[int]): + """Audio Channels property.""" + + ignored = { + 'object based', # Dolby Atmos + } + + def handle(self, value: typing.Union[int, str], context: typing.MutableMapping) -> typing.Optional[int]: + """Handle audio channels.""" + if isinstance(value, int): + return value + + if value.lower() not in self.ignored: + try: + return int(value) + except ValueError: + self.report(value, context) + return None + + +class AudioCodec(Configurable[str]): + """Audio codec property.""" + + @classmethod + def _extract_key(cls, value) -> str: + key = str(value).upper() + if key.startswith('A_'): + key = key[2:] + + # only the first part of the word. E.g.: 'AAC LC' => 'AAC' + return key.split(' ')[0] + + @classmethod + def _extract_fallback_key(cls, value, key) -> typing.Optional[str]: + if '/' in key: + return key.split('/')[0] + else: + return None diff --git a/knowit/properties/audio/__init__.py b/knowit/properties/audio/__init__.py deleted file mode 100644 index 31d30cb..0000000 --- a/knowit/properties/audio/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ - -from knowit.properties.audio.bitratemode import BitRateMode -from knowit.properties.audio.channels import AudioChannels -from knowit.properties.audio.codec import AudioCodec -from knowit.properties.audio.compression import AudioCompression -from knowit.properties.audio.profile import AudioProfile diff --git a/knowit/properties/audio/bitratemode.py b/knowit/properties/audio/bitratemode.py deleted file mode 100644 index 7d978d4..0000000 --- a/knowit/properties/audio/bitratemode.py +++ /dev/null @@ -1,6 +0,0 @@ - -from knowit.core import Configurable - - -class BitRateMode(Configurable[str]): - """Bit Rate mode property.""" diff --git a/knowit/properties/audio/channels.py b/knowit/properties/audio/channels.py deleted file mode 100644 index e9d22b6..0000000 --- a/knowit/properties/audio/channels.py +++ /dev/null @@ -1,23 +0,0 @@ -import typing - -from knowit.core import Property - - -class AudioChannels(Property[int]): - """Audio Channels property.""" - - ignored = { - 'object based', # Dolby Atmos - } - - def handle(self, value: typing.Union[int, str], context: typing.MutableMapping) -> typing.Optional[int]: - """Handle audio channels.""" - if isinstance(value, int): - return value - - if value.lower() not in self.ignored: - try: - return int(value) - except ValueError: - self.report(value, context) - return None diff --git a/knowit/properties/audio/codec.py b/knowit/properties/audio/codec.py deleted file mode 100644 index fe037c7..0000000 --- a/knowit/properties/audio/codec.py +++ /dev/null @@ -1,23 +0,0 @@ -import typing - -from knowit.core import Configurable - - -class AudioCodec(Configurable[str]): - """Audio codec property.""" - - @classmethod - def _extract_key(cls, value) -> str: - key = str(value).upper() - if key.startswith('A_'): - key = key[2:] - - # only the first part of the word. E.g.: 'AAC LC' => 'AAC' - return key.split(' ')[0] - - @classmethod - def _extract_fallback_key(cls, value, key) -> typing.Optional[str]: - if '/' in key: - return key.split('/')[0] - else: - return None diff --git a/knowit/properties/audio/compression.py b/knowit/properties/audio/compression.py deleted file mode 100644 index 233f07b..0000000 --- a/knowit/properties/audio/compression.py +++ /dev/null @@ -1,6 +0,0 @@ - -from knowit.core import Configurable - - -class AudioCompression(Configurable[str]): - """Audio Compression property.""" diff --git a/knowit/properties/audio/profile.py b/knowit/properties/audio/profile.py deleted file mode 100644 index df207b3..0000000 --- a/knowit/properties/audio/profile.py +++ /dev/null @@ -1,6 +0,0 @@ - -from knowit.core import Configurable - - -class AudioProfile(Configurable[str]): - """Audio profile property.""" From b70c7f8274f222815d7531cabf84cf0f14993e13 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 00:40:58 -0400 Subject: [PATCH 091/102] Convert subtitle packacge to module --- knowit/properties/{subtitle/format.py => subtitle.py} | 0 knowit/properties/subtitle/__init__.py | 2 -- 2 files changed, 2 deletions(-) rename knowit/properties/{subtitle/format.py => subtitle.py} (100%) delete mode 100644 knowit/properties/subtitle/__init__.py diff --git a/knowit/properties/subtitle/format.py b/knowit/properties/subtitle.py similarity index 100% rename from knowit/properties/subtitle/format.py rename to knowit/properties/subtitle.py diff --git a/knowit/properties/subtitle/__init__.py b/knowit/properties/subtitle/__init__.py deleted file mode 100644 index 9525628..0000000 --- a/knowit/properties/subtitle/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ - -from knowit.properties.subtitle.format import SubtitleFormat From 1ffa68638136447aacfe740e757ad73e3afa455b Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 00:40:09 -0400 Subject: [PATCH 092/102] Convert video package to module --- knowit/properties/video.py | 114 ++++++++++++++++++++++++++ knowit/properties/video/__init__.py | 10 --- knowit/properties/video/codec.py | 14 ---- knowit/properties/video/dimensions.py | 25 ------ knowit/properties/video/encoder.py | 6 -- knowit/properties/video/hdr.py | 6 -- knowit/properties/video/profile.py | 38 --------- knowit/properties/video/ratio.py | 35 -------- knowit/properties/video/scantype.py | 6 -- 9 files changed, 114 insertions(+), 140 deletions(-) create mode 100644 knowit/properties/video.py delete mode 100644 knowit/properties/video/__init__.py delete mode 100644 knowit/properties/video/codec.py delete mode 100644 knowit/properties/video/dimensions.py delete mode 100644 knowit/properties/video/encoder.py delete mode 100644 knowit/properties/video/hdr.py delete mode 100644 knowit/properties/video/profile.py delete mode 100644 knowit/properties/video/ratio.py delete mode 100644 knowit/properties/video/scantype.py diff --git a/knowit/properties/video.py b/knowit/properties/video.py new file mode 100644 index 0000000..666f0bc --- /dev/null +++ b/knowit/properties/video.py @@ -0,0 +1,114 @@ +import re +import typing +from decimal import Decimal + +from knowit.core import Configurable +from knowit.core import Property +from knowit.utils import round_decimal + + +class VideoCodec(Configurable[str]): + """Video Codec handler.""" + + @classmethod + def _extract_key(cls, value) -> str: + key = value.upper().split('/')[-1] + if key.startswith('V_'): + key = key[2:] + + return key.split(' ')[-1] + + +class VideoDimensions(Property[int]): + """Dimensions property.""" + + def __init__(self, *args: str, dimension='width' or 'height', **kwargs): + """Initialize the object.""" + super().__init__(*args, **kwargs) + self.dimension = dimension + + dimensions_re = re.compile(r'(?P\d+)x(?P\d+)') + + def handle(self, value, context) -> typing.Optional[int]: + """Handle ratio.""" + match = self.dimensions_re.match(value) + if match: + return int(match.groupdict().get(self.dimension)) + + self.report(value, context) + return None + + +class VideoEncoder(Configurable): + """Video Encoder property.""" + + +class VideoHdrFormat(Configurable): + """Video HDR Format property.""" + + +class VideoProfile(Configurable[str]): + """Video Profile property.""" + + @classmethod + def _extract_key(cls, value) -> str: + return value.upper().split('@')[0] + + +class VideoProfileLevel(Configurable[str]): + """Video Profile Level property.""" + + @classmethod + def _extract_key(cls, value) -> typing.Union[str, bool]: + values = str(value).upper().split('@') + if len(values) > 1: + value = values[1] + return value + + # There's no level, so don't warn or report it + return False + + +class VideoProfileTier(Configurable[str]): + """Video Profile Tier property.""" + + @classmethod + def _extract_key(cls, value) -> typing.Union[str, bool]: + values = str(value).upper().split('@') + if len(values) > 2: + return values[2] + + # There's no tier, so don't warn or report it + return False + + +class Ratio(Property[Decimal]): + """Ratio property.""" + + def __init__(self, *args: str, unit=None, **kwargs): + """Initialize the object.""" + super().__init__(*args, **kwargs) + self.unit = unit + + ratio_re = re.compile(r'(?P\d+)[:/](?P\d+)') + + def handle(self, value, context) -> typing.Optional[Decimal]: + """Handle ratio.""" + match = self.ratio_re.match(value) + if match: + width, height = match.groups() + 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 + + return result + + self.report(value, context) + return None + + +class ScanType(Configurable[str]): + """Scan Type property.""" diff --git a/knowit/properties/video/__init__.py b/knowit/properties/video/__init__.py deleted file mode 100644 index 889ec93..0000000 --- a/knowit/properties/video/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ - -from knowit.properties.video.codec import VideoCodec -from knowit.properties.video.dimensions import VideoDimensions -from knowit.properties.video.encoder import VideoEncoder -from knowit.properties.video.hdr import VideoHdrFormat -from knowit.properties.video.profile import VideoProfile -from knowit.properties.video.profile import VideoProfileLevel -from knowit.properties.video.profile import VideoProfileTier -from knowit.properties.video.ratio import Ratio -from knowit.properties.video.scantype import ScanType diff --git a/knowit/properties/video/codec.py b/knowit/properties/video/codec.py deleted file mode 100644 index 1b1f6a5..0000000 --- a/knowit/properties/video/codec.py +++ /dev/null @@ -1,14 +0,0 @@ - -from knowit.core import Configurable - - -class VideoCodec(Configurable[str]): - """Video Codec handler.""" - - @classmethod - def _extract_key(cls, value) -> str: - key = value.upper().split('/')[-1] - if key.startswith('V_'): - key = key[2:] - - return key.split(' ')[-1] diff --git a/knowit/properties/video/dimensions.py b/knowit/properties/video/dimensions.py deleted file mode 100644 index c4c933e..0000000 --- a/knowit/properties/video/dimensions.py +++ /dev/null @@ -1,25 +0,0 @@ - -import re -import typing - -from knowit.core import Property - - -class VideoDimensions(Property[int]): - """Dimensions property.""" - - def __init__(self, *args: str, dimension='width' or 'height', **kwargs): - """Initialize the object.""" - super().__init__(*args, **kwargs) - self.dimension = dimension - - dimensions_re = re.compile(r'(?P\d+)x(?P\d+)') - - def handle(self, value, context) -> typing.Optional[int]: - """Handle ratio.""" - match = self.dimensions_re.match(value) - if match: - return int(match.groupdict().get(self.dimension)) - - self.report(value, context) - return None diff --git a/knowit/properties/video/encoder.py b/knowit/properties/video/encoder.py deleted file mode 100644 index ecb2748..0000000 --- a/knowit/properties/video/encoder.py +++ /dev/null @@ -1,6 +0,0 @@ - -from knowit.core import Configurable - - -class VideoEncoder(Configurable): - """Video Encoder property.""" diff --git a/knowit/properties/video/hdr.py b/knowit/properties/video/hdr.py deleted file mode 100644 index a1f42b2..0000000 --- a/knowit/properties/video/hdr.py +++ /dev/null @@ -1,6 +0,0 @@ - -from knowit.core import Configurable - - -class VideoHdrFormat(Configurable): - """Video HDR Format property.""" diff --git a/knowit/properties/video/profile.py b/knowit/properties/video/profile.py deleted file mode 100644 index ca27c51..0000000 --- a/knowit/properties/video/profile.py +++ /dev/null @@ -1,38 +0,0 @@ -import typing - -from knowit.core import Configurable - - -class VideoProfile(Configurable[str]): - """Video Profile property.""" - - @classmethod - def _extract_key(cls, value) -> str: - return value.upper().split('@')[0] - - -class VideoProfileLevel(Configurable[str]): - """Video Profile Level property.""" - - @classmethod - def _extract_key(cls, value) -> typing.Union[str, bool]: - values = str(value).upper().split('@') - if len(values) > 1: - value = values[1] - return value - - # There's no level, so don't warn or report it - return False - - -class VideoProfileTier(Configurable[str]): - """Video Profile Tier property.""" - - @classmethod - def _extract_key(cls, value) -> typing.Union[str, bool]: - values = str(value).upper().split('@') - if len(values) > 2: - return values[2] - - # There's no tier, so don't warn or report it - return False diff --git a/knowit/properties/video/ratio.py b/knowit/properties/video/ratio.py deleted file mode 100644 index ef5f9ea..0000000 --- a/knowit/properties/video/ratio.py +++ /dev/null @@ -1,35 +0,0 @@ - -import re -import typing -from decimal import Decimal - -from knowit.core import Property -from knowit.utils import round_decimal - - -class Ratio(Property[Decimal]): - """Ratio property.""" - - def __init__(self, *args: str, unit=None, **kwargs): - """Initialize the object.""" - super().__init__(*args, **kwargs) - self.unit = unit - - ratio_re = re.compile(r'(?P\d+)[:/](?P\d+)') - - def handle(self, value, context) -> typing.Optional[Decimal]: - """Handle ratio.""" - match = self.ratio_re.match(value) - if match: - width, height = match.groups() - 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 - - return result - - self.report(value, context) - return None diff --git a/knowit/properties/video/scantype.py b/knowit/properties/video/scantype.py deleted file mode 100644 index 42b3c9f..0000000 --- a/knowit/properties/video/scantype.py +++ /dev/null @@ -1,6 +0,0 @@ - -from knowit.core import Configurable - - -class ScanType(Configurable[str]): - """Scan Type property.""" From c5fc3cd544db91c695e0216ee3592c03db317a9c Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:05:01 -0400 Subject: [PATCH 093/102] Merge general rules --- knowit/rules/{language.py => general.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename knowit/rules/{language.py => general.py} (100%) diff --git a/knowit/rules/language.py b/knowit/rules/general.py similarity index 100% rename from knowit/rules/language.py rename to knowit/rules/general.py From 7e510eb9c79fd1ae6c29a90c572147402b3c70f4 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:07:12 -0400 Subject: [PATCH 094/102] Convert audio rule package to module --- knowit/rules/audio.py | 102 +++++++++++++++++++++++++++++++++ knowit/rules/audio/__init__.py | 4 -- knowit/rules/audio/atmos.py | 18 ------ knowit/rules/audio/channels.py | 54 ----------------- knowit/rules/audio/dtshd.py | 30 ---------- 5 files changed, 102 insertions(+), 106 deletions(-) create mode 100644 knowit/rules/audio.py delete mode 100644 knowit/rules/audio/__init__.py delete mode 100644 knowit/rules/audio/atmos.py delete mode 100644 knowit/rules/audio/channels.py delete mode 100644 knowit/rules/audio/dtshd.py diff --git a/knowit/rules/audio.py b/knowit/rules/audio.py new file mode 100644 index 0000000..c467dc5 --- /dev/null +++ b/knowit/rules/audio.py @@ -0,0 +1,102 @@ +import typing +from decimal import Decimal +from logging import NullHandler, getLogger + +from knowit.core import Rule + +logger = getLogger(__name__) +logger.addHandler(NullHandler()) + + +class AtmosRule(Rule): + """Atmos rule.""" + + def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, + **kwargs): + super().__init__(name, **kwargs) + self.audio_codecs = getattr(config, 'AudioCodec') + + def execute(self, props, pv_props, context): + """Execute the rule against properties.""" + profile = context.get('profile') or 'default' + format_commercial = pv_props.get('format_commercial') + if 'codec' in props and format_commercial and 'atmos' in format_commercial.lower(): + props['codec'] = [props['codec'], + getattr(self.audio_codecs['ATMOS'], profile)] + + +class AudioChannelsRule(Rule): + """Audio Channel rule.""" + + mapping = { + 1: '1.0', + 2: '2.0', + 6: '5.1', + 8: '7.1', + } + + def execute(self, props, pv_props, context): + """Execute the rule against properties.""" + count = props.get('channels_count') + if count is None: + return + + channels = self.mapping.get(count) if isinstance(count, int) else None + positions = pv_props.get('channel_positions') or [] + positions = positions if isinstance(positions, list) else [positions] + candidate = 0 + for position in positions: + if not position: + continue + + c = Decimal('0.0') + for i in position.split('/'): + try: + c += Decimal(i) + except ValueError: + logger.debug('Invalid %s: %s', self.description, i) + pass + + c_count = int(c) + int(round((c - int(c)) * 10)) + if c_count == count: + return str(c) + + candidate = max(candidate, c) + + if channels: + return channels + + if candidate: + return candidate + + self.report(positions, context) + + +class DtsHdRule(Rule): + """DTS-HD rule.""" + + def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, + **kwargs): + super().__init__(name, **kwargs) + self.audio_codecs = getattr(config, 'AudioCodec') + self.audio_profiles = getattr(config, 'AudioProfile') + + @classmethod + def _redefine(cls, props, name, index): + actual = props.get(name) + if isinstance(actual, list): + value = actual[index] + if value is None: + del props[name] + else: + props[name] = value + + def execute(self, props, pv_props, context): + """Execute the rule against properties.""" + profile = context.get('profile') or 'default' + + if props.get('codec') == getattr(self.audio_codecs['DTS'], + profile) and props.get('profile') in ( + getattr(self.audio_profiles['MA'], profile), + getattr(self.audio_profiles['HRA'], profile)): + props['codec'] = getattr(self.audio_codecs['DTS-HD'], profile) diff --git a/knowit/rules/audio/__init__.py b/knowit/rules/audio/__init__.py deleted file mode 100644 index 6f8dde3..0000000 --- a/knowit/rules/audio/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ - -from knowit.rules.audio.atmos import AtmosRule -from knowit.rules.audio.channels import AudioChannelsRule -from knowit.rules.audio.dtshd import DtsHdRule diff --git a/knowit/rules/audio/atmos.py b/knowit/rules/audio/atmos.py deleted file mode 100644 index 5a32c4d..0000000 --- a/knowit/rules/audio/atmos.py +++ /dev/null @@ -1,18 +0,0 @@ -import typing - -from knowit.core import Rule - - -class AtmosRule(Rule): - """Atmos rule.""" - - def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, **kwargs): - super().__init__(name, **kwargs) - self.audio_codecs = getattr(config, 'AudioCodec') - - def execute(self, props, pv_props, context): - """Execute the rule against properties.""" - profile = context.get('profile') or 'default' - format_commercial = pv_props.get('format_commercial') - if 'codec' in props and format_commercial and 'atmos' in format_commercial.lower(): - props['codec'] = [props['codec'], getattr(self.audio_codecs['ATMOS'], profile)] diff --git a/knowit/rules/audio/channels.py b/knowit/rules/audio/channels.py deleted file mode 100644 index ce876b3..0000000 --- a/knowit/rules/audio/channels.py +++ /dev/null @@ -1,54 +0,0 @@ -from decimal import Decimal -from logging import NullHandler, getLogger - -from knowit.core import Rule - -logger = getLogger(__name__) -logger.addHandler(NullHandler()) - - -class AudioChannelsRule(Rule): - """Audio Channel rule.""" - - mapping = { - 1: '1.0', - 2: '2.0', - 6: '5.1', - 8: '7.1', - } - - def execute(self, props, pv_props, context): - """Execute the rule against properties.""" - count = props.get('channels_count') - if count is None: - return - - channels = self.mapping.get(count) if isinstance(count, int) else None - positions = pv_props.get('channel_positions') or [] - positions = positions if isinstance(positions, list) else [positions] - candidate = 0 - for position in positions: - if not position: - continue - - c = Decimal('0.0') - for i in position.split('/'): - try: - c += Decimal(i) - except ValueError: - logger.debug('Invalid %s: %s', self.description, i) - pass - - c_count = int(c) + int(round((c - int(c)) * 10)) - if c_count == count: - return str(c) - - candidate = max(candidate, c) - - if channels: - return channels - - if candidate: - return candidate - - self.report(positions, context) diff --git a/knowit/rules/audio/dtshd.py b/knowit/rules/audio/dtshd.py deleted file mode 100644 index e736f1a..0000000 --- a/knowit/rules/audio/dtshd.py +++ /dev/null @@ -1,30 +0,0 @@ -import typing - -from knowit.core import Rule - - -class DtsHdRule(Rule): - """DTS-HD rule.""" - - def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, **kwargs): - super().__init__(name, **kwargs) - self.audio_codecs = getattr(config, 'AudioCodec') - self.audio_profiles = getattr(config, 'AudioProfile') - - @classmethod - def _redefine(cls, props, name, index): - actual = props.get(name) - if isinstance(actual, list): - value = actual[index] - if value is None: - del props[name] - else: - props[name] = value - - def execute(self, props, pv_props, context): - """Execute the rule against properties.""" - profile = context.get('profile') or 'default' - - if props.get('codec') == getattr(self.audio_codecs['DTS'], profile) and props.get('profile') in ( - getattr(self.audio_profiles['MA'], profile), getattr(self.audio_profiles['HRA'], profile)): - props['codec'] = getattr(self.audio_codecs['DTS-HD'], profile) From c52f1b5687817aaf92127a18a37904b097932a7e Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:07:49 -0400 Subject: [PATCH 095/102] Convert video rule package to module --- knowit/rules/{video/resolution.py => video.py} | 0 knowit/rules/video/__init__.py | 2 -- 2 files changed, 2 deletions(-) rename knowit/rules/{video/resolution.py => video.py} (100%) delete mode 100644 knowit/rules/video/__init__.py diff --git a/knowit/rules/video/resolution.py b/knowit/rules/video.py similarity index 100% rename from knowit/rules/video/resolution.py rename to knowit/rules/video.py diff --git a/knowit/rules/video/__init__.py b/knowit/rules/video/__init__.py deleted file mode 100644 index e453f17..0000000 --- a/knowit/rules/video/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ - -from knowit.rules.video.resolution import ResolutionRule From 99d8aab81f7f65df8ab3677ceda452f7dab3348a Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:09:11 -0400 Subject: [PATCH 096/102] Convert subtitle rule package to module --- .../{subtitle/closedcaption.py => subtitle.py} | 13 ++++++++++++- knowit/rules/subtitle/__init__.py | 3 --- knowit/rules/subtitle/hearingimpaired.py | 16 ---------------- 3 files changed, 12 insertions(+), 20 deletions(-) rename knowit/rules/{subtitle/closedcaption.py => subtitle.py} (57%) delete mode 100644 knowit/rules/subtitle/__init__.py delete mode 100644 knowit/rules/subtitle/hearingimpaired.py diff --git a/knowit/rules/subtitle/closedcaption.py b/knowit/rules/subtitle.py similarity index 57% rename from knowit/rules/subtitle/closedcaption.py rename to knowit/rules/subtitle.py index dd61c49..fa16fdb 100644 --- a/knowit/rules/subtitle/closedcaption.py +++ b/knowit/rules/subtitle.py @@ -1,4 +1,3 @@ - import re from knowit.core import Rule @@ -14,3 +13,15 @@ def execute(self, props, pv_props, context): for name in (pv_props.get('_closed_caption'), props.get('name')): if name and self.cc_re.search(name): return True + + +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 diff --git a/knowit/rules/subtitle/__init__.py b/knowit/rules/subtitle/__init__.py deleted file mode 100644 index 55ef30a..0000000 --- a/knowit/rules/subtitle/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ - -from knowit.rules.subtitle.closedcaption import ClosedCaptionRule -from knowit.rules.subtitle.hearingimpaired import HearingImpairedRule diff --git a/knowit/rules/subtitle/hearingimpaired.py b/knowit/rules/subtitle/hearingimpaired.py deleted file mode 100644 index 150855f..0000000 --- a/knowit/rules/subtitle/hearingimpaired.py +++ /dev/null @@ -1,16 +0,0 @@ - -import re - -from knowit.core import Rule - - -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 From 9da2e187f2c5d0136eade85e7ae285bf0e5773d5 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:12:01 -0400 Subject: [PATCH 097/102] Merge general rules --- knowit/rules/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowit/rules/__init__.py b/knowit/rules/__init__.py index 7eda2b0..90a943a 100644 --- a/knowit/rules/__init__.py +++ b/knowit/rules/__init__.py @@ -2,7 +2,7 @@ from knowit.rules.audio import AtmosRule from knowit.rules.audio import AudioChannelsRule from knowit.rules.audio import DtsHdRule -from knowit.rules.language import LanguageRule +from knowit.rules.general import LanguageRule from knowit.rules.subtitle import ClosedCaptionRule from knowit.rules.subtitle import HearingImpairedRule from knowit.rules.video import ResolutionRule From c70dcb87b0b3c254b8cad75bb800fbf47b64ab83 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:21:33 -0400 Subject: [PATCH 098/102] Merge general rules --- knowit/properties/audio.py | 4 ++-- knowit/provider.py | 2 +- knowit/providers/enzyme.py | 2 +- knowit/providers/ffmpeg.py | 2 +- knowit/providers/mediainfo.py | 9 +++------ knowit/providers/mkvmerge.py | 2 +- knowit/rules/audio.py | 2 ++ knowit/utils.py | 1 - 8 files changed, 11 insertions(+), 13 deletions(-) diff --git a/knowit/properties/audio.py b/knowit/properties/audio.py index 66c4e2d..8347420 100644 --- a/knowit/properties/audio.py +++ b/knowit/properties/audio.py @@ -1,6 +1,6 @@ import typing -from knowit.core import Property -from knowit.core import Configurable + +from knowit.core import Configurable, Property class BitRateMode(Configurable[str]): diff --git a/knowit/provider.py b/knowit/provider.py index 62be920..f8c29f5 100755 --- a/knowit/provider.py +++ b/knowit/provider.py @@ -4,8 +4,8 @@ from logging import NullHandler, getLogger import knowit.config -from knowit.properties import Quantity from knowit.core import Property, Rule +from knowit.properties import Quantity from knowit.units import units logger = getLogger(__name__) diff --git a/knowit/providers/enzyme.py b/knowit/providers/enzyme.py index b41700f..5dd3d8c 100644 --- a/knowit/providers/enzyme.py +++ b/knowit/providers/enzyme.py @@ -5,6 +5,7 @@ from logging import NullHandler, getLogger import enzyme +from knowit.core import Property from knowit.properties import ( AudioCodec, Basic, @@ -14,7 +15,6 @@ VideoCodec, YesNo, ) -from knowit.core import Property from knowit.provider import ( MalformedFileError, Provider, diff --git a/knowit/providers/ffmpeg.py b/knowit/providers/ffmpeg.py index 99b82ee..2474408 100644 --- a/knowit/providers/ffmpeg.py +++ b/knowit/providers/ffmpeg.py @@ -6,6 +6,7 @@ from subprocess import check_output from knowit import VIDEO_EXTENSIONS +from knowit.core import Property from knowit.properties import ( AudioChannels, AudioCodec, @@ -22,7 +23,6 @@ VideoProfileLevel, YesNo, ) -from knowit.core import Property from knowit.provider import ( MalformedFileError, Provider, diff --git a/knowit/providers/mediainfo.py b/knowit/providers/mediainfo.py index 73e166b..39fd403 100644 --- a/knowit/providers/mediainfo.py +++ b/knowit/providers/mediainfo.py @@ -9,8 +9,9 @@ from pymediainfo import MediaInfo from pymediainfo import __version__ as pymediainfo_version -from .. import VIDEO_EXTENSIONS -from ..properties import ( +from knowit import VIDEO_EXTENSIONS +from knowit.core import MultiValue, Property +from knowit.properties import ( AudioChannels, AudioCodec, AudioCompression, @@ -29,10 +30,6 @@ VideoProfileTier, YesNo, ) -from knowit.core import ( - MultiValue, - Property, -) from knowit.provider import ( MalformedFileError, Provider, diff --git a/knowit/providers/mkvmerge.py b/knowit/providers/mkvmerge.py index f7c77c1..e5aca15 100644 --- a/knowit/providers/mkvmerge.py +++ b/knowit/providers/mkvmerge.py @@ -6,6 +6,7 @@ from logging import NullHandler, getLogger from subprocess import check_output +from knowit.core import Property from knowit.properties import ( AudioCodec, Basic, @@ -16,7 +17,6 @@ VideoDimensions, YesNo, ) -from knowit.core import Property from knowit.provider import ( MalformedFileError, Provider, diff --git a/knowit/rules/audio.py b/knowit/rules/audio.py index c467dc5..b35d364 100644 --- a/knowit/rules/audio.py +++ b/knowit/rules/audio.py @@ -13,6 +13,7 @@ class AtmosRule(Rule): def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, **kwargs): + """Initialize an Atmos rule.""" super().__init__(name, **kwargs) self.audio_codecs = getattr(config, 'AudioCodec') @@ -77,6 +78,7 @@ class DtsHdRule(Rule): def __init__(self, config: typing.Mapping[str, typing.Mapping], name: str, **kwargs): + """Initialize a DTS-HD Rule.""" super().__init__(name, **kwargs) self.audio_codecs = getattr(config, 'AudioCodec') self.audio_profiles = getattr(config, 'AudioProfile') diff --git a/knowit/utils.py b/knowit/utils.py index 6845fb0..2bb0ed7 100644 --- a/knowit/utils.py +++ b/knowit/utils.py @@ -132,4 +132,3 @@ def round_decimal(value: Decimal, min_digits=0, max_digits: typing.Optional[int] if max_digits: return round(value, min(max_digits, decimal_places)) return value - From 2fa8b9a30694a0cb08a3bc2d712cc91140df25ed Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:27:03 -0400 Subject: [PATCH 099/102] Fix handling video dimensions if value is None --- knowit/properties/video.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/knowit/properties/video.py b/knowit/properties/video.py index 666f0bc..e1b293d 100644 --- a/knowit/properties/video.py +++ b/knowit/properties/video.py @@ -33,7 +33,13 @@ def handle(self, value, context) -> typing.Optional[int]: """Handle ratio.""" match = self.dimensions_re.match(value) if match: - return int(match.groupdict().get(self.dimension)) + match_dict = match.groupdict() + try: + value = match_dict[self.dimension] + except KeyError: + pass + else: + return int(value) self.report(value, context) return None From 23a145439b86a8634a5a7c10137adbcab5a44248 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:28:01 -0400 Subject: [PATCH 100/102] Fix type-hints --- knowit/core.py | 4 ++-- knowit/properties/general.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/knowit/core.py b/knowit/core.py index 3de7d93..7619dc7 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -158,7 +158,7 @@ def handle(self, value, context): class MultiValue(Property): """Property with multiple values.""" - def __init__(self, prop: typing.Optional[Property[typing.Any]] = None, delimiter='/', single=False, + def __init__(self, prop: typing.Optional[Property] = None, delimiter='/', single=False, handler=None, name=None, **kwargs): """Init method.""" super().__init__(*(prop.names if prop else (name,)), **kwargs) @@ -170,7 +170,7 @@ def __init__(self, prop: typing.Optional[Property[typing.Any]] = None, delimiter def handle( self, value: str, - context: typing.Mapping, + context: typing.MutableMapping, ) -> typing.Union[T, typing.List[T]]: """Handle properties with multiple values.""" call = self.handler or self.prop.handle diff --git a/knowit/properties/general.py b/knowit/properties/general.py index 9841bd0..c522f87 100644 --- a/knowit/properties/general.py +++ b/knowit/properties/general.py @@ -44,7 +44,7 @@ class Duration(Property[timedelta]): r'(?P\d{3})' r'(?P\d{3})?\d*)?') - def __init__(self, *args: str, resolution: int or Decimal = 1, **kwargs): + def __init__(self, *args: str, resolution: typing.Union[int, Decimal] = 1, **kwargs): """Initialize a Duration.""" super().__init__(*args, **kwargs) self.resolution = resolution From ab90a13400b3de3e13e1a10a1f59f81ba094d497 Mon Sep 17 00:00:00 2001 From: Labrys Date: Mon, 29 Mar 2021 01:37:20 -0400 Subject: [PATCH 101/102] Raise if no handler available for MultiValueProperty --- knowit/core.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/knowit/core.py b/knowit/core.py index 7619dc7..9736d7b 100644 --- a/knowit/core.py +++ b/knowit/core.py @@ -173,7 +173,13 @@ def handle( context: typing.MutableMapping, ) -> typing.Union[T, typing.List[T]]: """Handle properties with multiple values.""" - call = self.handler or self.prop.handle + if self.handler: + call = self.handler + elif self.prop: + call = self.prop.handle + else: + raise NotImplementedError('No handler available') + result = call(value, context) if result is not None: return result From 74a1425651f6f830d2118cbaf5986d3fde564a9f Mon Sep 17 00:00:00 2001 From: Rato Date: Thu, 8 Apr 2021 11:38:30 +0200 Subject: [PATCH 102/102] Release 0.4.0 --- README.rst | 95 +++++++++++++++++++++++++--------------------- knowit/__init__.py | 4 +- knowit/__main__.py | 4 +- 3 files changed, 56 insertions(+), 47 deletions(-) diff --git a/README.rst b/README.rst index 678024b..68001d8 100644 --- a/README.rst +++ b/README.rst @@ -25,11 +25,11 @@ CLI Extract information from a video file:: $ knowit /folder/Audio Samples/hd_dtsma_7.1.mkv - For: /folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv - Knowit 0.2.4 found: + For: /folder/Audio Samples/hd_dtsma_7.1.mkv + Knowit 0.4.0 found: { "title": "7.1Ch DTS-HD MA - Speaker Mapping Test File", - "path": "/folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "path": "/folder/Audio Samples/hd_dtsma_7.1.mkv", "duration": "0:01:37", "size": "40.77 MB", "bit_rate": "3.3 Mbps", @@ -41,8 +41,8 @@ Extract information from a video file:: "width": "1920 pixel", "height": "1080 pixel", "scan_type": "Progressive", - "aspect_ratio": 1.778, - "pixel_aspect_ratio": 1.0, + "aspect_ratio": "1.778", + "pixel_aspect_ratio": "1.0", "resolution": "1080p", "frame_rate": "23.976 FPS", "bit_depth": "8 bit", @@ -50,8 +50,7 @@ Extract information from a video file:: "profile": "Main", "profile_level": "4", "media_type": "video/H264", - "default": true, - "language": "Undetermined" + "default": true } ], "audio": [ @@ -71,18 +70,24 @@ Extract information from a video file:: "default": true } ], - "provider": "libmediainfo.so.0" + "provider": { + "name": "mediainfo", + "version": { + "pymediainfo": "5.0.3", + "libmediainfo.so.0": "v20.9" + } + } } -Extract information from a video file using ffprobe:: +Extract information from a video file using ffmpeg:: $ knowit --provider ffmpeg /folder/Audio Samples/hd_dtsma_7.1.mkv - For: /folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv - Knowit 0.2.4 found: + For: /folder/Audio Samples/hd_dtsma_7.1.mkv + Knowit 0.4.0 found: { "title": "7.1Ch DTS-HD MA - Speaker Mapping Test File", - "path": "/folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "path": "/folder/Audio Samples/hd_dtsma_7.1.mkv", "duration": "0:01:37", "size": "40.77 MB", "bit_rate": "3.3 Mbps", @@ -93,15 +98,14 @@ Extract information from a video file using ffprobe:: "width": "1920 pixel", "height": "1080 pixel", "scan_type": "Progressive", - "aspect_ratio": 1.778, - "pixel_aspect_ratio": 1.0, + "aspect_ratio": "1.778", + "pixel_aspect_ratio": "1.0", "resolution": "1080p", "frame_rate": "23.976 FPS", "bit_depth": "8 bit", "codec": "H.264", "profile": "Main", - "default": true, - "language": "Undetermined" + "default": true } ], "audio": [ @@ -118,18 +122,24 @@ Extract information from a video file using ffprobe:: "default": true } ], - "provider": "ffprobe" + "provider": { + "name": "ffmpeg", + "version": { + "ffprobe": "v4.2.4-1ubuntu0.1" + } + } } + Using docker:: docker run -it --rm -v /folder:/folder knowit /folder/Audio Samples/hd_dtsma_7.1.mkv - For: /folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv - Knowit 0.2.4 found: + For: /folder/Audio Samples/hd_dtsma_7.1.mkv + Knowit 0.4.0 found: { "title": "7.1Ch DTS-HD MA - Speaker Mapping Test File", - "path": "/folder/Audio Samples/7.1Ch DTS-HD MA - Speaker Mapping Test File.mkv", + "path": "/folder/Audio Samples/hd_dtsma_7.1.mkv", "duration": "0:01:37", "size": "40.77 MB", "bit_rate": "3.3 Mbps", @@ -141,8 +151,8 @@ Using docker:: "width": "1920 pixel", "height": "1080 pixel", "scan_type": "Progressive", - "aspect_ratio": 1.778, - "pixel_aspect_ratio": 1.0, + "aspect_ratio": "1.778", + "pixel_aspect_ratio": "1.0", "resolution": "1080p", "frame_rate": "23.976 FPS", "bit_depth": "8 bit", @@ -150,8 +160,7 @@ Using docker:: "profile": "Main", "profile_level": "4", "media_type": "video/H264", - "default": true, - "language": "Undetermined" + "default": true } ], "audio": [ @@ -171,18 +180,20 @@ Using docker:: "default": true } ], - "provider": "libmediainfo.so.0" + "provider": { + "name": "mediainfo", + "version": { + "pymediainfo": "5.0.3", + "libmediainfo.so.0": "v20.9" + } + } } - All available CLI options:: $ knowit --help - usage: knowit [-h] [-p PROVIDER] [-E] [-v] [-r] [--report] [-y] [-N] - [-P PROFILE] [--mediainfo MEDIAINFO] [--ffmpeg FFMPEG] - [--version] - [videopath [videopath ...]] + usage: knowit [-h] [-p PROVIDER] [--debug] [--report] [-y] [-N] [-P PROFILE] [--mediainfo MEDIAINFO] [--ffmpeg FFMPEG] [--mkvmerge MKVMERGE] [--version] [videopath [videopath ...]] positional arguments: videopath Path to the video to introspect @@ -192,25 +203,21 @@ All available CLI options:: Providers: -p PROVIDER, --provider PROVIDER - The provider to be used: mediainfo, ffmpeg or enzyme. - - Input: - -E, --fail-on-error Fail when errors are found on the media file. + The provider to be used: mediainfo, ffmpeg, mkvmerge or enzyme. Output: - -v, --verbose Display debug output - -r, --raw Display raw properties + --debug Print information for debugging knowit and for reporting bugs. --report Parse media and report all non-detected values -y, --yaml Display output in yaml format -N, --no-units Display output without units -P PROFILE, --profile PROFILE - Display values according to specified profile: code, - default, human, technical + Display values according to specified profile: code, default, human, technical Configuration: --mediainfo MEDIAINFO The location to search for MediaInfo binaries - --ffmpeg FFMPEG The location to search for FFmpeg (ffprobe) binaries + --ffmpeg FFMPEG The location to search for ffprobe (FFmpeg) binaries + --mkvmerge MKVMERGE The location to search for mkvmerge (MKVToolNix) binaries Information: --version Display knowit version. @@ -229,12 +236,14 @@ the ``--user`` flag. External dependencies ------------------------- -KnowIt can use MediaInfo or FFmpeg (ffprobe) +KnowIt can use MediaInfo, ffprobe (FFmpeg) or mkvmerge (MKVToolNix) -KnowIt supports MKV regardless if MediaInfo or FFmpeg are installed. +KnowIt supports MKV regardless if MediaInfo, FFmpeg or MKVToolNix are installed. -MediaInfo or FFmpeg increases the number of supported formats and the number of extracted information. +MediaInfo, FFmpeg or MKVToolNix increases the number of supported formats and the number of extracted information. MediaInfo is the default provider. Visit their `website `_ and install the proper package for your system. -FFmpeg (ffprobe) can be downloaded `here `_ +ffprobe (FFmpeg) can be downloaded `here `_ + +mkvmerge (MKVToolNix) can be downloaded `here `_ diff --git a/knowit/__init__.py b/knowit/__init__.py index 14dfc5e..eda7067 100644 --- a/knowit/__init__.py +++ b/knowit/__init__.py @@ -1,10 +1,10 @@ """Know your media files better.""" __title__ = 'knowit' -__version__ = '0.4.0-dev' +__version__ = '0.4.0' __short_version__ = '.'.join(__version__.split('.')[:2]) __author__ = 'Rato AQ2' __license__ = 'MIT' -__copyright__ = 'Copyright 2016-2017, Rato AQ2' +__copyright__ = 'Copyright 2016-2021, Rato AQ2' __url__ = 'https://github.com/ratoaq2/knowit' #: Video extensions diff --git a/knowit/__main__.py b/knowit/__main__.py index 93a2331..c301484 100644 --- a/knowit/__main__.py +++ b/knowit/__main__.py @@ -92,13 +92,13 @@ def build_argument_parser() -> ArgumentParser: conf_opts.add_argument( '--ffmpeg', dest='ffmpeg', - help='The location to search for FFmpeg (ffprobe) binaries', + help='The location to search for ffprobe (FFmpeg) binaries', type=str, ) conf_opts.add_argument( '--mkvmerge', dest='mkvmerge', - help='The location to search for mkvmerge binaries', + help='The location to search for mkvmerge (MKVToolNix) binaries', type=str, )