import os import typing from logging import NullHandler, getLogger import knowit.config from knowit.core import Property, Rule from knowit.properties import Quantity from knowit.units import units logger = getLogger(__name__) logger.addHandler(NullHandler()) size_property = Quantity('size', unit=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.""" min_fps = 10 max_fps = 200 def __init__( self, config: knowit.config.Config, mapping: PropertyConfig, rules: typing.Optional[RuleConfig] = None, ): """Init method.""" self.config = config self.mapping = mapping self.rules = rules or {} def accepts(self, target): """Whether or not the video is supported by this provider.""" raise NotImplementedError def describe(self, target, context): """Read video metadata information.""" raise NotImplementedError def _describe_tracks(self, video_path, general_track, video_tracks, audio_tracks, subtitle_tracks, context): logger.debug('Handling general track') props = self._describe_track(general_track, 'general', context) if 'path' not in props: props['path'] = video_path if 'container' not in props: props['container'] = os.path.splitext(video_path)[1][1:] if 'size' not in props and os.path.isfile(video_path): props['size'] = size_property.handle(os.path.getsize(video_path), context) for track_type, tracks, in (('video', video_tracks), ('audio', audio_tracks), ('subtitle', subtitle_tracks)): results = [] for track in tracks or []: logger.debug('Handling %s track', track_type) t = self._validate_track(track_type, self._describe_track(track, track_type, context)) if t: results.append(t) if results: props[track_type] = results return props @classmethod def _validate_track(cls, track_type, track): if track_type != 'video' or 'frame_rate' not in track: return track frame_rate = track['frame_rate'] try: frame_rate = frame_rate.magnitude except AttributeError: pass if cls.min_fps < frame_rate < cls.max_fps: return track def _describe_track(self, track, track_type, context): """Describe track to a dict. :param track: :param track_type: :rtype: dict """ props = {} pv_props = {} for name, prop in self.mapping[track_type].items(): if not prop: # placeholder to be populated by rules. It keeps the order props[name] = None continue value = prop.extract_value(track, context) if value is not None: which = props if not prop.private else pv_props which[name] = value for name, rule in self.rules.get(track_type, {}).items(): if props.get(name) is not None and not rule.override: logger.debug('Skipping rule %s since property is already present: %r', name, props[name]) continue value = rule.execute(props, pv_props, context) if value is not None: which = props if not rule.private else pv_props which[name] = value elif name in props and (not rule.override or props[name] is None): del props[name] return props @property def version(self): """Return provider version information.""" raise NotImplementedError class ProviderError(Exception): """Base class for provider exceptions.""" pass class MalformedFileError(ProviderError): """Malformed File error.""" pass class UnsupportedFileFormatError(ProviderError): """Unsupported File Format error.""" pass