Coverage for lib/lottie/objects/animation.py: 67%
105 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
1from .base import LottieObject, LottieProp, PseudoBool, Index
2from .layers import Layer, PreCompLayer
3from .assets import Asset, Precomp
4from .text import FontList, Chars
5from .composition import Composition
6from .helpers import VisualObject, Marker
8##\defgroup Lottie Lottie
9#
10# Objects of the lottie file structure.
12## \defgroup LottieCheck Lottie (to check)
13#
14# Lottie objects that have not been tested
17## @ingroup Lottie
18class Metadata(LottieObject):
19 """!
20 Document metadata
21 """
22 _props = [
23 LottieProp("author", "a", str, False),
24 LottieProp("generator", "g", str, False),
25 LottieProp("keywords", "k", str, True),
26 LottieProp("description", "d", str, False),
27 LottieProp("theme_color", "tc", str, False),
28 ]
30 def __init__(self):
31 self.generator = None
32 self.author = None
33 self.keywords = None
34 self.description = None
35 self.theme_color = None
38#ingroup Lottie
39class UserMetadata(LottieObject):
40 """!
41 User-defined metadata
42 """
43 _props = [
44 LottieProp("filename", "filename", str, False),
45 LottieProp("custom_properties", "customProps", dict, False),
46 ]
48 def __init__(self):
49 super().__init__()
51 self.filename = None
52 self.custom_properties = {}
55#ingroup Lottie
56class MotionBlur(LottieObject):
57 """!
58 Motion blur settings
59 """
60 _props = [
61 LottieProp("shutter_angle", "sa", float, False),
62 LottieProp("shutter_phase", "sp", float, False),
63 LottieProp("samples_per_frame", "spf", float, False),
64 LottieProp("adaptive_sample_limit", "asl", float, False),
65 ]
67 def __init__(self):
68 super().__init__()
70 ## Angle in degrees
71 self.shutter_angle = None
72 ## Angle in degrees
73 self.shutter_phase = None
74 self.samples_per_frame = None
75 self.adaptive_sample_limit = None
78## @ingroup Lottie
79class Animation(Composition, VisualObject):
80 """!
81 Top level object, describing the animation
83 @see http://docs.aenhancers.com/items/compitem/
84 """
85 _props = [
86 LottieProp("version", "v", str, False),
87 LottieProp("frame_rate", "fr", float, False),
88 LottieProp("in_point", "ip", float, False),
89 LottieProp("out_point", "op", float, False),
90 LottieProp("width", "w", int, False),
91 LottieProp("height", "h", int, False),
92 LottieProp("threedimensional", "ddd", PseudoBool, False),
93 LottieProp("assets", "assets", Asset, True),
94 LottieProp("extra_compositions", "comps", Precomp, True),
95 LottieProp("fonts", "fonts", FontList),
96 LottieProp("chars", "chars", Chars, True),
97 LottieProp("markers", "markers", Marker, True),
98 LottieProp("motion_blur", "mb", MotionBlur, False),
99 LottieProp("metadata", "meta", Metadata, False),
100 LottieProp("user_metadata", "metadata", UserMetadata, False),
101 ]
102 _version = "5.5.2"
104 def __init__(self, n_frames=60, framerate=60):
105 super().__init__()
106 ## The time when the composition work area begins, in frames.
107 self.in_point = 0
108 ## The time when the composition work area ends.
109 ## Sets the final Frame of the animation
110 self.out_point = n_frames
111 ## Frames per second
112 self.frame_rate = framerate
113 ## Composition Width
114 self.width = 512
115 ## Composition has 3-D layers
116 self.threedimensional = False
117 ## Composition Height
118 self.height = 512
119 ## Bodymovin Version
120 self.version = self._version
121 ## source items that can be used in multiple places. Comps and Images for now.
122 self.assets = [] # Image, Precomp
123 ## List of Extra compositions not referenced by anything
124 self.extra_compositions = None
125 ## source chars for text layers
126 self.chars = None
127 ## Available fonts
128 self.fonts = None
129 self.metadata = None
130 self.user_metadata = None
131 self.motion_blur = None
132 self.markers = None
134 def precomp(self, name):
135 for ass in self.assets:
136 if isinstance(ass, Precomp) and ass.id == name:
137 return ass
138 return None
140 def _on_prepare_layer(self, layer):
141 if layer.in_point is None:
142 layer.in_point = self.in_point
143 if layer.out_point is None:
144 layer.out_point = self.out_point
146 def to_precomp(self):
147 """!
148 Turns the main comp into a precomp
149 """
150 precomp = Precomp()
151 #precomp.frame_rate = self.frame_rate
152 precomp.layers = self.layers
153 precomp.name = self.name
154 name_id = 0
155 base_name = self.name or "Animation"
156 name = base_name
157 index = 0
158 while True:
159 if index >= len(self.assets): 159 ↛ 162line 159 didn't jump to line 162, because the condition on line 159 was never false
160 break
162 while self.assets[index].id == name:
163 name_id += 1
164 name = "%s %s" % (base_name, name_id)
165 index = -1
167 index += 1
168 precomp.id = name
169 self.assets.append(precomp)
171 precomp_layer = PreCompLayer()
172 precomp_layer.width = self.width
173 precomp_layer.height = self.height
174 precomp_layer.in_point = self.in_point
175 precomp_layer.out_point = self.out_point
176 precomp_layer.reference_id = name
177 self.layers = [precomp_layer]
178 return precomp
180 def scale(self, width, height):
181 """!
182 Scales the animation so it fits in width/height
183 """
184 if self.width != width or self.height != height:
185 self.to_precomp()
187 scale = min(width/self.width, height/self.height)
188 self.width = width
189 self.height = height
191 self.layers[0].transform.scale.value *= scale
193 def tgs_sanitize(self):
194 """!
195 Cleans up some things to ensure it works as a telegram sticker
196 """
197 self.scale(512, 512)
199 if self.frame_rate < 45:
200 self.frame_rate = 30
201 else:
202 self.frame_rate = 60
204 def _fixup(self):
205 super()._fixup()
206 if self.assets:
207 for ass in self.assets:
208 if isinstance(ass, Precomp):
209 ass.animation = self
210 ass._fixup()
212 def __str__(self):
213 return self.name or super().__str__()