Skip to content

Commit

Permalink
Release 0.1.2: Handling DVD-PAL resolutions; PY3 and API fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ratoaq2 committed Oct 25, 2016
1 parent 18119da commit e2d1eea
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 64 deletions.
17 changes: 8 additions & 9 deletions knowit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,12 @@
from __future__ import unicode_literals

__title__ = 'knowit'
__version__ = '0.1.1'
__version__ = '0.1.2'
__short_version__ = '.'.join(__version__.split('.')[:2])
__author__ = 'Rato AQ2'
__license__ = 'MIT'
__copyright__ = 'Copyright 2016, Rato AQ2'

try:
from collections import OrderedDict
except ImportError: # pragma: no cover
from ordereddict import OrderedDict

from .api import knowit


#: Video extensions
VIDEO_EXTENSIONS = ('.3g2', '.3gp', '.3gp2', '.3gpp', '.60d', '.ajp', '.asf', '.asx', '.avchd', '.avi', '.bik',
'.bix', '.box', '.cam', '.dat', '.divx', '.dmf', '.dv', '.dvr-ms', '.evo', '.flc', '.fli',
Expand All @@ -25,3 +17,10 @@
'.mpe', '.mpeg', '.mpg', '.mpv', '.mpv2', '.mxf', '.nsv', '.nut', '.ogg', '.ogm', '.ogv', '.omf',
'.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', '.vivo',
'.vob', '.vro', '.wm', '.wmv', '.wmx', '.wrap', '.wvx', '.wx', '.x264', '.xvid')

try:
from collections import OrderedDict
except ImportError: # pragma: no cover
from ordereddict import OrderedDict

from .api import know
4 changes: 2 additions & 2 deletions knowit/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ def build_argument_parser():
def knowit(video_path, options):
"""Extract video metadata."""
console.info('For: %s', video_path)
console.info('Knowit %s found: ', __version__)
info = api.knowit(video_path, vars(options))
info = api.know(video_path, vars(options))
if not options.no_output:
console.info('Knowit %s found: ', __version__)
if options.yaml:
result = yaml.dump({video_path: info}, Dumper=CustomDumper,
default_flow_style=False, allow_unicode=True)
Expand Down
5 changes: 3 additions & 2 deletions knowit/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
])


def knowit(video_path, options):
def know(video_path, options=None):
"""Return a dict containing the video metadata.
:param video_path:
Expand All @@ -22,8 +22,9 @@ def knowit(video_path, options):
:return:
:rtype: dict
"""
options = options or dict()
for name, provider in available_providers.items():
if name != (options['provider'] or name):
if name != (options.get('provider') or name):
continue

if provider.accepts(video_path):
Expand Down
15 changes: 8 additions & 7 deletions knowit/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,19 +412,20 @@ def handle(self, props, context):
scan_type = props.get('scan_type', 'p')[0].lower()

if width and height:
# Max DAR for widescreen TVs is 16:9
max_dar = min(dar, self.wide)
# selected DAR must be between 4:3 and 16:9
selected_dar = max(min(dar, self.wide), self.square)
stretched_width = int(round(width * par / 16)) * 16 # mod-16
calculated_height = int(round(stretched_width / max_dar / 8)) * 8 # mod-8
calculated_height = int(round(stretched_width / selected_dar / 8)) * 8 # mod-8

last_resolution = None
selected_resolution = None
for r in reversed(self.resolutions):
if r < calculated_height:
break
last_resolution = r

if last_resolution:
return '{0}{1}'.format(last_resolution, scan_type)
selected_resolution = r

if selected_resolution:
return '{0}{1}'.format(selected_resolution, scan_type)

logger.info('''# Unable to detect resolution
- width: {0}
Expand Down
28 changes: 12 additions & 16 deletions knowit/providers/enzyme.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import logging

import enzyme
from six import string_types

from .. import OrderedDict
from ..properties import (
Expand Down Expand Up @@ -64,25 +63,22 @@ def __init__(self):
]),
})

def accepts(self, target):
def accepts(self, video_path):
"""Accept only MKV files."""
return not isinstance(target, string_types) or target.lower().endswith('.mkv')
return video_path.lower().endswith('.mkv')

def describe(self, target, options):
def describe(self, video_path, options):
"""Return video metadata."""
if isinstance(target, string_types):
try:
with open(target, 'rb') as f:
data = todict(enzyme.MKV(f))
except enzyme.MalformedMKVError: # pragma: no cover
logger.warning("Invalid file '%s'", target)
if options['fail_on_error']:
raise MalformedFileError
return dict()
else:
data = target
try:
with open(video_path, 'rb') as f:
data = todict(enzyme.MKV(f))
except enzyme.MalformedMKVError: # pragma: no cover
logger.warning("Invalid file '%s'", video_path)
if options.get('fail_on_error'):
raise MalformedFileError
return dict()

if options['raw']:
if options.get('raw'):
return data

return self._describe_tracks(data.get('info'), data.get('video_tracks'),
Expand Down
17 changes: 8 additions & 9 deletions knowit/providers/mediainfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
import sys

from pymediainfo import MediaInfo
from six import string_types

from .. import OrderedDict
from .. import OrderedDict, VIDEO_EXTENSIONS
from ..properties import (
AudioChannels, AudioChannelsRule, AudioCodec, AudioCompression, AudioProfile, BitRateMode,
Duration, Float, HearingImpairedRule, Integer, Language, MultiHandler, Property,
Expand Down Expand Up @@ -135,14 +134,14 @@ def __init__(self):
]),
})

def accepts(self, target):
def accepts(self, video_path):
"""Accept any video when MediaInfo is available."""
return load_native()
return load_native() and video_path.lower().endswith(VIDEO_EXTENSIONS)

def describe(self, target, options):
def describe(self, video_path, options):
"""Return video metadata."""
data = MediaInfo.parse(target).to_data() if isinstance(target, string_types) else target
if options['raw']:
data = MediaInfo.parse(video_path).to_data()
if options.get('raw'):
return data

general_tracks = []
Expand All @@ -163,8 +162,8 @@ def describe(self, target, options):
result = self._describe_tracks(general_tracks[0] if general_tracks else [],
video_tracks, audio_tracks, subtitle_tracks)
if not result:
logger.warning("Invalid file '%s'", target)
if options['fail_on_error']:
logger.warning("Invalid file '%s'", video_path)
if options.get('fail_on_error'):
raise MalformedFileError

return result
8 changes: 5 additions & 3 deletions knowit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
from datetime import timedelta

import babelfish
from six import string_types
import yaml


def todict(obj, classkey=None):
"""Transform an object to dict."""
if isinstance(obj, dict):
if isinstance(obj, string_types):
return obj
elif isinstance(obj, dict):
data = {}
for (k, v) in obj.items():
data[k] = todict(v, classkey)
Expand All @@ -26,8 +29,7 @@ def todict(obj, classkey=None):
if classkey is not None and hasattr(obj, '__class__'):
data[classkey] = obj.__class__.__name__
return data
else:
return obj
return obj


class CustomDumper(yaml.SafeDumper):
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def find_version(*file_paths):
test_requirements = ['flake8_docstrings', 'flake8-import-order', 'pydocstyle!=1.1.0', 'pep8-naming',
'pytest', 'pytest>=2.8', 'pytest-cov', 'pytest-flake8']

if sys.version_info < (3, 3):
test_requirements.append('mock')

setup(
name='knowit',
version=find_version('knowit', '__init__.py'),
Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import re
from datetime import timedelta

import pytest
from six import text_type
from yaml.constructor import ConstructorError
from yaml.nodes import MappingNode

Expand Down Expand Up @@ -45,3 +47,8 @@ def construct_mapping(self, node, deep=False):


CustomLoader.add_constructor(u'tag:yaml.org,2002:map', construct_mapping)


@pytest.fixture
def video_path(tmpdir):
return text_type(tmpdir.ensure('video.mkv'))
13 changes: 10 additions & 3 deletions tests/test_enzyme.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import glob
import os

try:
from mock import Mock
except:
from unittest.mock import Mock

import pytest
import yaml

Expand All @@ -29,12 +34,14 @@ def _parameters():


@pytest.mark.parametrize('raw,expected', _parameters())
def test_enzyme_provider(raw, expected):
def test_enzyme_provider(monkeypatch, video_path, raw, expected):
# Given
options = dict(provider='enzyme', raw=False)
options = dict(provider='enzyme')
monkeypatch.setattr('enzyme.MKV', Mock())
monkeypatch.setattr('knowit.utils.todict', lambda mkv: raw)

# When
actual = knowit.knowit(raw, options)
actual = knowit.know(video_path, options)

# Then
assert expected == actual
14 changes: 11 additions & 3 deletions tests/test_mediainfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import glob
import os

try:
from mock import Mock
except:
from unittest.mock import Mock

import pytest
import yaml

Expand All @@ -30,14 +35,17 @@ def _parameters():


@pytest.mark.parametrize('raw,expected', _parameters())
def test_mediainfo_provider(monkeypatch, raw, expected):
def test_mediainfo_provider(monkeypatch, video_path, raw, expected):
# Given
options = dict(provider='mediainfo', raw=False)
options = dict(provider='mediainfo')
parse_method = Mock()
parse_method.to_data.return_value = raw
monkeypatch.setattr('pymediainfo.MediaInfo.parse', staticmethod(lambda video: parse_method))
monkeypatch.setattr('knowit.providers.mediainfo.INITIALIZED', True)
monkeypatch.setattr('knowit.providers.mediainfo.MEDIA_INFO_AVAILABLE', True)

# When
actual = knowit.knowit(raw, options)
actual = knowit.know(video_path, options)

# Then
assert expected == actual
41 changes: 31 additions & 10 deletions tests/test_resolution.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
# https://en.wikipedia.org/wiki/Pixel_aspect_ratio
# https://en.wikipedia.org/wiki/Low-definition_television
# https://knowledge.kaltura.com/best-practices-multi-device-transcoding

288p:
- width: 480
height: 270
scan_type: Progressive
aspect_ratio: 1.778
pixel_aspect_ratio: 1.0
- width: 480
height: 270
scan_type: Progressive
240p:
- width: 320
height: 250
scan_type: Progressive
Expand All @@ -28,6 +19,16 @@
height: 262
scan_type: Progressive

288p:
- width: 480
height: 270
scan_type: Progressive
aspect_ratio: 1.778
pixel_aspect_ratio: 1.0
- width: 480
height: 270
scan_type: Progressive

360p:
- width: 640
height: 360
Expand Down Expand Up @@ -105,6 +106,26 @@
scan_type: Progressive
aspect_ratio: 1.818
pixel_aspect_ratio: 1.422
- width: 720
height: 596
scan_type: Progressive
aspect_ratio: 1.289
pixel_aspect_ratio: 1.067
- width: 720
height: 586
scan_type: Progressive
aspect_ratio: 1.311
pixel_aspect_ratio: 1.067
- width: 720
height: 588
scan_type: Progressive
aspect_ratio: 1.304
pixel_aspect_ratio: 1.065
- width: 720
height: 590
scan_type: Progressive
aspect_ratio: 1.302
pixel_aspect_ratio: 1.067

720p:
- width: 1280
Expand Down

0 comments on commit e2d1eea

Please sign in to comment.