import json import logging from collections import defaultdict from logging import NullHandler, getLogger import enzyme from knowit.core import Property from knowit.properties import ( AudioCodec, Basic, Duration, Language, Quantity, VideoCodec, YesNo, ) from knowit.provider import ( MalformedFileError, Provider, ) from knowit.rules import ( AudioChannelsRule, ClosedCaptionRule, HearingImpairedRule, LanguageRule, ResolutionRule, ) from knowit.rules.general import GuessTitleRule from knowit.serializer import get_json_encoder from knowit.units import units from knowit.utils import to_dict logger = getLogger(__name__) logger.addHandler(NullHandler()) class EnzymeProvider(Provider): """Enzyme Provider.""" def __init__(self, config, *args, **kwargs): """Init method.""" super().__init__(config, { 'general': { 'title': Property('title', description='media title'), 'duration': Duration('duration', description='media duration'), }, 'video': { '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', 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')), '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', 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', 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', 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 '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': { 'guessed': GuessTitleRule('guessed properties', private=True), 'language': LanguageRule('video language', override=True), 'resolution': ResolutionRule('video resolution'), }, 'audio': { 'guessed': GuessTitleRule('guessed properties', private=True), 'language': LanguageRule('audio language', override=True), 'channels': AudioChannelsRule('audio channels'), }, 'subtitle': { 'guessed': GuessTitleRule('guessed properties', private=True), 'language': LanguageRule('subtitle language', override=True), 'hearing_impaired': HearingImpairedRule('subtitle hearing impaired', override=True), 'closed_caption': ClosedCaptionRule('closed caption', override=True), } }) def accepts(self, video_path): """Accept only MKV files.""" return video_path.lower().endswith('.mkv') @classmethod def extract_info(cls, video_path): """Extract info from the video.""" with open(video_path, 'rb') as f: return to_dict(enzyme.MKV(f)) def describe(self, video_path, context): """Return video metadata.""" try: data = defaultdict(dict) ff = self.extract_info(video_path) def debug_data(): """Debug data.""" return json.dumps(ff, 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 enzyme %r has raw data:\n%s', video_path, enzyme.__version__, debug_data) data.update(ff) if 'info' in data and data['info'] is None: return {} except enzyme.MalformedMKVError: # pragma: no cover raise MalformedFileError if logger.level == logging.DEBUG: logger.debug('Video {video_path} scanned using Enzyme {version} has raw data:\n{data}', video_path=video_path, version=enzyme.__version__, data=json.dumps(data, cls=get_json_encoder(context), indent=4, ensure_ascii=False)) result = self._describe_tracks(video_path, data.get('info', {}), data.get('video_tracks'), data.get('audio_tracks'), data.get('subtitle_tracks'), context) if not result: raise MalformedFileError result['provider'] = { 'name': 'enzyme', 'version': self.version } return result @property def version(self): """Return enzyme version information.""" return {'enzyme': enzyme.__version__}