python-lottie  0.6.11+deva0e77a1
A framework to work with lottie files and telegram animated stickers (tgs)
base.py
Go to the documentation of this file.
1 import enum
2 import inspect
3 import importlib
4 from ..nvector import NVector
5 from ..utils.color import Color, ColorMode
6 
7 
8 class LottieBase:
9  """!
10  Base class for Lottie JSON objects bindings
11  """
12  def to_dict(self):
13  """!
14  Serializes into a JSON object fit for the Lottie format
15  """
16  raise NotImplementedError
17 
18  @classmethod
19  def load(cls, lottiedict):
20  """!
21  Loads from a JSON object
22  @returns An instance of the class
23  """
24  raise NotImplementedError
25 
26  def clone(self):
27  """!
28  Returns a copy of the object
29  """
30  raise NotImplementedError
31 
32 
33 class EnumMeta(enum.EnumMeta):
34  """!
35  Hack to counter-hack the hack in enum meta
36  """
37  def __new__(cls, name, bases, classdict):
38  classdict["__reduce_ex__"] = lambda *a, **kw: None # pragma: no cover
39  return super().__new__(cls, name, bases, classdict)
40 
41 
42 class LottieEnum(LottieBase, enum.Enum, metaclass=EnumMeta):
43  """!
44  Base class for enum-like types in the Lottie JSON structure
45  """
46  def to_dict(self):
47  return self.value
48 
49  @classmethod
50  def load(cls, lottieint):
51  return cls(lottieint)
52 
53  def clone(self):
54  return self
55 
56 
57 class PseudoList:
58  """!
59  List tag for some weird values in the Lottie JSON
60  """
61  pass
62 
63 
64 class LottieValueConverter:
65  """!
66  Factory for property types that require special conversions
67  """
68  def __init__(self, py, lottie, name=None):
69  self.pypy = py
70  self.lottielottie = lottie
71  self.namename = name or "%s but displayed as %s" % (self.pypy.__name__, self.lottielottie.__name__)
72 
73  def py_to_lottie(self, val):
74  return self.lottielottie(val)
75 
76  def lottie_to_py(self, val):
77  return self.pypy(val)
78 
79  @property
80  def __name__(self):
81  return self.namename
82 
83 
84 ## For values in Lottie that are bools but ints in the JSON
85 PseudoBool = LottieValueConverter(bool, int, "0-1 int")
86 
87 
88 class LottieProp:
89  """!
90  Lottie <-> Python property mapper
91  """
92  def __init__(self, name, lottie, type=float, list=False, cond=None):
93  ## Name of the Python property
94  self.namename = name
95  ## Name of the Lottie JSON property
96  self.lottielottie = lottie
97  ## Type of the property
98  ## @see LottieValueConverter, PseudoBool
99  self.typetype = type
100  ## Whether the property is a list of self.type
101  ## @see PseudoList
102  self.listlist = list
103  ## Condition on when the property is loaded from the Lottie JSON
104  self.condcond = cond
105 
106  def get(self, obj):
107  """!
108  Returns the value of the property from a Python object
109  """
110  return getattr(obj, self.namename)
111 
112  def set(self, obj, value):
113  """!
114  Sets the value of the property from a Python object
115  """
116  if isinstance(getattr(obj.__class__, self.namename, None), property):
117  return
118  return setattr(obj, self.namename, value)
119 
120  def load_from_parent(self, lottiedict):
121  """!
122  Returns the value for this property from a JSON dict representing the parent object
123  @returns The loaded value or @c None if the property is not in @p lottiedict
124  """
125  if self.lottielottie in lottiedict:
126  return self.loadload(lottiedict[self.lottielottie])
127  return None
128 
129  def load_into(self, lottiedict, obj):
130  """!
131  Loads from a Lottie dict into an object
132  """
133  if self.condcond and not self.condcond(lottiedict):
134  return
135  self.setset(obj, self.load_from_parentload_from_parent(lottiedict))
136 
137  def load(self, lottieval):
138  """!
139  Loads the property from a JSON value
140  @returns the Python equivalent of the JSON value
141  """
142  if self.listlist is PseudoList and isinstance(lottieval, list):
143  return self._load_scalar_load_scalar(lottieval[0])
144  elif self.listlist is True:
145  return list(filter(lambda x: x is not None, (
146  self._load_scalar_load_scalar(it)
147  for it in lottieval
148  )))
149  return self._load_scalar_load_scalar(lottieval)
150 
151  def _load_scalar(self, lottieval):
152  if lottieval is None:
153  return None
154  if inspect.isclass(self.typetype) and issubclass(self.typetype, LottieBase):
155  return self.typetype.load(lottieval)
156  elif isinstance(self.typetype, type) and isinstance(lottieval, self.typetype):
157  return lottieval
158  elif isinstance(self.typetype, LottieValueConverter):
159  return self.typetype.lottie_to_py(lottieval)
160  elif self.typetype is NVector:
161  return NVector(*lottieval)
162  elif self.typetype is Color:
163  return Color(*lottieval)
164  if isinstance(lottieval, list) and lottieval:
165  lottieval = lottieval[0]
166  return self.typetype(lottieval)
167 
168  def to_dict(self, obj):
169  """!
170  Converts the value of the property as from @p obj into a JSON value
171  @param obj LottieObject with this property
172  """
173  val = self.getget(obj)
174  if isinstance(self.typetype, LottieValueConverter):
175  val = self.typetype.py_to_lottie(val)
176 
177  val = self._basic_to_dict_basic_to_dict(val)
178 
179  if self.listlist is PseudoList:
180  if not isinstance(obj, list):
181  return [val]
182  return val
183 
184  def _basic_to_dict(self, v):
185  if isinstance(v, LottieBase):
186  return v.to_dict()
187  elif isinstance(v, Color):
188  return list(map(self._basic_to_dict_basic_to_dict, v.to_rgb().components))
189  elif isinstance(v, NVector):
190  return list(map(self._basic_to_dict_basic_to_dict, v.components))
191  elif isinstance(v, (list, tuple)):
192  return list(map(self._basic_to_dict_basic_to_dict, v))
193  elif isinstance(v, (int, str, bool)):
194  return v
195  elif isinstance(v, float):
196  if v % 1 == 0:
197  return int(v)
198  return v # round(v, 3)
199  else:
200  raise Exception("Unknown value {!r}".format(v))
201 
202  def __repr__(self):
203  return "<LottieProp %s:%s>" % (self.namename, self.lottielottie)
204 
205  def clone_value(self, value):
206  if isinstance(value, (list, tuple)):
207  return [self.clone_valueclone_value(v) for v in value]
208  if isinstance(value, (LottieBase, NVector)):
209  return value.clone()
210  if isinstance(value, (int, float, bool, str)) or value is None:
211  return value
212  raise Exception("Could not convert {!r}".format(value))
213 
214 
216  def __new__(cls, name, bases, attr):
217  props = []
218  for base in bases:
219  if type(base) == cls:
220  props += base._props
221  attr["_props"] = props + attr.get("_props", [])
222  return super().__new__(cls, name, bases, attr)
223 
224 
225 class LottieObject(LottieBase, metaclass=LottieObjectMeta):
226  """!
227  @brief Base class for mapping Python classes into Lottie JSON objects
228  """
229  def __init__(self):
230  pass
231 
232  def to_dict(self):
233  return {
234  prop.lottie: prop.to_dict(self)
235  for prop in self._props
236  if prop.get(self) is not None
237  }
238 
239  @classmethod
240  def load(cls, lottiedict):
241  if "__pyclass" in lottiedict:
242  return CustomObject.load(lottiedict)
243  if not lottiedict:
244  return None
245  cls = cls._load_get_class_load_get_class(lottiedict)
246  obj = cls()
247  for prop in cls._props:
248  prop.load_into(lottiedict, obj)
249  return obj
250 
251  @classmethod
252  def _load_get_class(cls, lottiedict):
253  return cls
254 
255  def find(self, search, propname="name"):
256  """!
257  @param search The value of the property to search
258  @param propname The name of the property used to search
259  @brief Recursively searches for child objects with a matching property
260  """
261  if getattr(self, propname, None) == search:
262  return self
263  for prop in self._props:
264  v = prop.get(self)
265  if isinstance(v, LottieObject):
266  found = v.find(search, propname)
267  if found:
268  return found
269  elif isinstance(v, list) and v and isinstance(v[0], LottieObject):
270  for obj in v:
271  found = obj.find(search, propname)
272  if found:
273  return found
274  return None
275 
276  def find_all(self, type, predicate=None, include_self=True):
277  """!
278  Find all child objects that match a predicate
279  @param type Type (or tuple of types) of the objects to match
280  @param predicate Function that returns true on the objects to find
281  @param include_self Whether should counsider `self` for a potential match
282  """
283 
284  if isinstance(self, type) and include_self:
285  if not predicate or predicate(self):
286  yield self
287 
288  for prop in self._props:
289  v = prop.get(self)
290 
291  if isinstance(v, LottieObject):
292  for found in v.find_all(type, predicate, True):
293  yield found
294  elif isinstance(v, list) and v and isinstance(v[0], LottieObject):
295  for child in v:
296  for found in child.find_all(type, predicate, True):
297  yield found
298 
299  def clone(self):
300  obj = self.__class__()
301  for prop in self._props:
302  v = prop.get(self)
303  prop.set(obj, prop.clone_value(v))
304  return obj
305 
306  def clone_into(self, other):
307  for prop in self._props:
308  v = prop.get(self)
309  prop.set(other, prop.clone_value(v))
310 
311  def __str__(self):
312  return type(self).__name__
313 
314 
315 class Index:
316  """!
317  @brief Simple iterator to generate increasing integers
318  """
319  def __init__(self):
320  self._i_i = -1
321 
322  def __next__(self):
323  self._i_i += 1
324  return self._i_i
325 
326 
328  """!
329  Allows extending the Lottie shapes with custom Python classes
330  """
331  wrapped_lottie = LottieObject
332 
333  def __init__(self):
334  self.wrappedwrapped = self.wrapped_lottiewrapped_lottie()
335 
336  @classmethod
337  def load(cls, lottiedict):
338  ld = lottiedict.copy()
339  classname = ld.pop("__pyclass")
340  modn, clsn = classname.rsplit(".", 1)
341  subcls = getattr(importlib.import_module(modn), clsn)
342  obj = subcls()
343  for prop in subcls._props:
344  prop.load_into(lottiedict, obj)
345  obj.wrapped = subcls.wrapped_lottie.load(ld)
346  return obj
347 
348  def clone(self):
349  obj = self.__class__(**self.to_pyctor())
350  obj.wrapped = self.wrappedwrapped.clone()
351  return obj
352 
353  def to_dict(self):
354  dict = self.wrappedwrapped.to_dict()
355  dict["__pyclass"] = "{0.__module__}.{0.__name__}".format(self.__class__)
356  dict.update(LottieObject.to_dict(self))
357  return dict
358 
359  def _build_wrapped(self):
360  return self.wrapped_lottiewrapped_lottie()
361 
362  def refresh(self):
363  self.wrappedwrapped = self._build_wrapped_build_wrapped()
364 
365 
367  DONT_RECURSE = object()
368 
369  def __call__(self, lottie_object):
370  self._process_process(lottie_object)
371 
372  def _process(self, lottie_object):
373  self.visitvisit(lottie_object)
374  for p in lottie_object._props:
375  pval = p.get(lottie_object)
376  if self.visit_propertyvisit_property(lottie_object, p, pval) is not self.DONT_RECURSEDONT_RECURSE:
377  if isinstance(pval, LottieObject):
378  self._process_process(pval)
379  elif isinstance(pval, list) and pval and isinstance(pval[0], LottieObject):
380  for c in pval:
381  self._process_process(c)
382 
383  def visit(self, object):
384  pass
385 
386  def visit_property(self, object, property, value):
387  pass
Allows extending the Lottie shapes with custom Python classes.
Definition: base.py:327
def clone(self)
Returns a copy of the object.
Definition: base.py:348
def load(cls, lottiedict)
Loads from a JSON object.
Definition: base.py:337
def to_dict(self)
Serializes into a JSON object fit for the Lottie format.
Definition: base.py:353
Hack to counter-hack the hack in enum meta.
Definition: base.py:33
def __new__(cls, name, bases, classdict)
Definition: base.py:37
Simple iterator to generate increasing integers.
Definition: base.py:315
def __init__(self)
Definition: base.py:319
def __next__(self)
Definition: base.py:322
Base class for Lottie JSON objects bindings.
Definition: base.py:8
def to_dict(self)
Serializes into a JSON object fit for the Lottie format.
Definition: base.py:12
def load(cls, lottiedict)
Loads from a JSON object.
Definition: base.py:19
def clone(self)
Returns a copy of the object.
Definition: base.py:26
Base class for enum-like types in the Lottie JSON structure.
Definition: base.py:42
def to_dict(self)
Serializes into a JSON object fit for the Lottie format.
Definition: base.py:46
def clone(self)
Returns a copy of the object.
Definition: base.py:53
def load(cls, lottieint)
Loads from a JSON object.
Definition: base.py:50
def __new__(cls, name, bases, attr)
Definition: base.py:216
Base class for mapping Python classes into Lottie JSON objects.
Definition: base.py:225
def clone_into(self, other)
Definition: base.py:306
def _load_get_class(cls, lottiedict)
Definition: base.py:252
def clone(self)
Returns a copy of the object.
Definition: base.py:299
def find_all(self, type, predicate=None, include_self=True)
Find all child objects that match a predicate.
Definition: base.py:276
def load(cls, lottiedict)
Loads from a JSON object.
Definition: base.py:240
def to_dict(self)
Serializes into a JSON object fit for the Lottie format.
Definition: base.py:232
def find(self, search, propname="name")
Recursively searches for child objects with a matching property.
Definition: base.py:255
Lottie <-> Python property mapper.
Definition: base.py:88
lottie
Name of the Lottie JSON property.
Definition: base.py:96
cond
Condition on when the property is loaded from the Lottie JSON.
Definition: base.py:104
name
Name of the Python property.
Definition: base.py:94
def load_into(self, lottiedict, obj)
Loads from a Lottie dict into an object.
Definition: base.py:129
def _basic_to_dict(self, v)
Definition: base.py:184
def load_from_parent(self, lottiedict)
Returns the value for this property from a JSON dict representing the parent object.
Definition: base.py:120
def __init__(self, name, lottie, type=float, list=False, cond=None)
Definition: base.py:92
def get(self, obj)
Returns the value of the property from a Python object.
Definition: base.py:106
def load(self, lottieval)
Loads the property from a JSON value.
Definition: base.py:137
def clone_value(self, value)
Definition: base.py:205
list
Whether the property is a list of self.type.
Definition: base.py:102
def _load_scalar(self, lottieval)
Definition: base.py:151
type
Type of the property.
Definition: base.py:99
def set(self, obj, value)
Sets the value of the property from a Python object.
Definition: base.py:112
def to_dict(self, obj)
Converts the value of the property as from obj into a JSON value.
Definition: base.py:168
Factory for property types that require special conversions.
Definition: base.py:64
def __init__(self, py, lottie, name=None)
Definition: base.py:68
def visit_property(self, object, property, value)
Definition: base.py:386
def __call__(self, lottie_object)
Definition: base.py:369
def _process(self, lottie_object)
Definition: base.py:372
def visit(self, object)
Definition: base.py:383
List tag for some weird values in the Lottie JSON.
Definition: base.py:57