Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Pull in _NodeBase from actorgraph; include short-hash in repr
  • Loading branch information
kkroening committed Jul 4, 2017
commit fc946be164712e67dfe7f4da10ab637d75f6958c
86 changes: 74 additions & 12 deletions ffmpeg/nodes.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,72 @@
from __future__ import unicode_literals

from builtins import object
import copy
import hashlib
import json


class Node(object):
"""Node base"""
def __init__(self, parents, name, *args, **kwargs):
def _recursive_repr(item):
"""Hack around python `repr` to deterministically represent dictionaries.

This is able to represent more things than json.dumps, since it does not require things to be JSON serializable
(e.g. datetimes).
"""
if isinstance(item, basestring):
result = str(item)
elif isinstance(item, list):
result = '[{}]'.format(', '.join([_recursive_repr(x) for x in item]))
elif isinstance(item, dict):
kv_pairs = ['{}: {}'.format(_recursive_repr(k), _recursive_repr(item[k])) for k in sorted(item)]
result = '{' + ', '.join(kv_pairs) + '}'
else:
result = repr(item)
return result


def _create_hash(item):
hasher = hashlib.sha224()
repr_ = _recursive_repr(item)
hasher.update(repr_.encode('utf-8'))
return hasher.hexdigest()


class _NodeBase(object):
@property
def hash(self):
if self._hash is None:
self._update_hash()
return self._hash

def __init__(self, parents, name):
parent_hashes = [hash(parent) for parent in parents]
assert len(parent_hashes) == len(set(parent_hashes)), 'Same node cannot be included as parent multiple times'
self._parents = parents
self._hash = None
self._name = name
self._args = args
self._kwargs = kwargs

def _transplant(self, new_parents):
other = copy.copy(self)
other._parents = copy.copy(new_parents)
return other

@property
def _repr_args(self):
raise NotImplementedError()

@property
def _repr_kwargs(self):
raise NotImplementedError()

@property
def _short_hash(self):
return '{:x}'.format(abs(hash(self)))[:12]

def __repr__(self):
formatted_props = ['{}'.format(arg) for arg in self._args]
formatted_props += ['{}={!r}'.format(key, self._kwargs[key]) for key in sorted(self._kwargs)]
return '{}({})'.format(self._name, ','.join(formatted_props))
args = self._repr_args
kwargs = self._repr_kwargs
formatted_props = ['{!r}'.format(arg) for arg in args]
formatted_props += ['{}={!r}'.format(key, kwargs[key]) for key in sorted(kwargs)]
return '{}({}) <{}>'.format(self._name, ', '.join(formatted_props), self._short_hash)

def __hash__(self):
if self._hash is None:
Expand All @@ -30,16 +77,31 @@ def __eq__(self, other):
return hash(self) == hash(other)

def _update_hash(self):
props = {'args': self._args, 'kwargs': self._kwargs}
props_str = json.dumps(props, sort_keys=True).encode('utf-8')
my_hash = hashlib.md5(props_str).hexdigest()
props = {'args': self._repr_args, 'kwargs': self._repr_kwargs}
my_hash = _create_hash(props)
parent_hashes = [str(hash(parent)) for parent in self._parents]
hashes = parent_hashes + [my_hash]
hashes_str = ','.join(hashes).encode('utf-8')
hash_str = hashlib.md5(hashes_str).hexdigest()
self._hash = int(hash_str, base=16)


class Node(_NodeBase):
"""Node base"""
def __init__(self, parents, name, *args, **kwargs):
super(Node, self).__init__(parents, name)
self._args = args
self._kwargs = kwargs

@property
def _repr_args(self):
return self._args

@property
def _repr_kwargs(self):
return self._kwargs


class InputNode(Node):
"""InputNode type"""
def __init__(self, name, *args, **kwargs):
Expand Down
12 changes: 6 additions & 6 deletions ffmpeg/tests/test_ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ def test_repr():
trim3 = ffmpeg.trim(in_file, start_frame=50, end_frame=60)
concatted = ffmpeg.concat(trim1, trim2, trim3)
output = ffmpeg.output(concatted, 'dummy2.mp4')
assert repr(in_file) == "input(filename={!r})".format('dummy.mp4')
assert repr(trim1) == "trim(end_frame=20,start_frame=10)"
assert repr(trim2) == "trim(end_frame=40,start_frame=30)"
assert repr(trim3) == "trim(end_frame=60,start_frame=50)"
assert repr(concatted) == "concat(n=3)"
assert repr(output) == "output(filename={!r})".format('dummy2.mp4')
assert repr(in_file) == "input(filename={!r}) <{}>".format('dummy.mp4', in_file._short_hash)
assert repr(trim1) == "trim(end_frame=20, start_frame=10) <{}>".format(trim1._short_hash)
assert repr(trim2) == "trim(end_frame=40, start_frame=30) <{}>".format(trim2._short_hash)
assert repr(trim3) == "trim(end_frame=60, start_frame=50) <{}>".format(trim3._short_hash)
assert repr(concatted) == "concat(n=3) <{}>".format(concatted._short_hash)
assert repr(output) == "output(filename={!r}) <{}>".format('dummy2.mp4', output._short_hash)


def test_get_args_simple():
Expand Down