Coverage for lib/lottie/objects/assets.py: 27%
114 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
1import os
2import re
3import base64
4import mimetypes
5from io import BytesIO
6from .base import LottieProp, PseudoBool, Index
7from .layers import Layer
8from .composition import Composition
9from .helpers import VisualObject
12## @ingroup Lottie
13class Asset(VisualObject):
14 _props = [
15 LottieProp("id", "id", str, False),
16 ]
18 @classmethod
19 def _load_get_class(cls, lottiedict):
20 if lottiedict.get("t", None) == 3:
21 return DataSource
22 if "p" in lottiedict or "u" in lottiedict:
23 if "w" in lottiedict:
24 return Image
25 return Sound
26 if "layers" in lottiedict:
27 return Precomp
30#ingroup Lottie
31class FileAsset(Asset):
32 """!
33 Asset referencing a file
34 """
35 _props = [
36 LottieProp("path", "u", str, False),
37 LottieProp("file_name", "p", str, False),
38 LottieProp("is_embedded", "e", PseudoBool, False),
39 ]
41 def __init__(self):
42 super().__init__()
44 ## Path to the directory containing a file
45 self.path = ""
46 ## Filename or data url
47 self.file_name = ""
48 ## Whether the file is embedded
49 self.is_embedded = False
51 def _id_from_file(self, file):
52 if not self.id:
53 if isinstance(file, str):
54 self.id = os.path.basename(file)
55 elif hasattr(file, "name"):
56 self.id = os.path.basename(file.name)
57 elif hasattr(file, "filename"):
58 self.id = os.path.basename(file.filename)
59 else:
60 self.id = "image_%s" % id(self)
62 def data(self):
63 """
64 Returns a tuple (format, data) with the contents of the file
66 `format` is a string like "png", and `data` is just raw binary data.
68 If it's impossible to fetch this info, returns (None, None)
69 """
70 if self.is_embedded:
71 m = re.match("data:[^/]+/([^;,]+);base64,(.*)", self.file_name)
72 if m:
73 return m.group(1), base64.b64decode(m.group(2))
74 return None, None
75 path = self.path + self.file_name
76 if os.path.isfile(path):
77 with open(path, "rb") as imgfile:
78 return os.path.splitext(path)[1][1:], imgfile.read()
79 return None, None
82## @ingroup Lottie
83class Image(FileAsset):
84 """!
85 External image
87 @see http://docs.aenhancers.com/sources/filesource/
88 """
89 _props = [
90 LottieProp("height", "h", float, False),
91 LottieProp("width", "w", float, False),
92 LottieProp("type", "t", str, False),
93 ]
95 @staticmethod
96 def guess_mime(file):
97 if isinstance(file, str):
98 filename = file
99 elif hasattr(file, "name"):
100 filename = file.name
101 else:
102 return "application/octet-stream"
103 return mimetypes.guess_type(filename)
105 def __init__(self, id=""):
106 super().__init__()
108 ## Image Height
109 self.height = 0
110 ## Image Width
111 self.width = 0
112 ## Image ID
113 self.id = id
114 ## If "seq", marks it as part of an image sequence
115 self.type = None
117 def load(self, file, format=None):
118 """!
119 @param file Filename, file object, or PIL.Image.Image to load
120 @param format Format to store the image data as
121 """
122 from PIL import Image
124 if not isinstance(file, Image.Image):
125 image = Image.open(file)
126 else:
127 image = file
129 self._id_from_file(file)
131 self.path = ""
132 if format is None:
133 format = (image.format or "png").lower()
134 self.width, self.height = image.size
135 output = BytesIO()
136 image.save(output, format=format)
137 self.file_name = "data:image/%s;base64,%s" % (
138 format,
139 base64.b64encode(output.getvalue()).decode("ascii")
140 )
141 self.is_embedded = True
142 return self
144 @classmethod
145 def embedded(cls, image, format=None):
146 """!
147 Create an object from an image file
148 """
149 lottie_image = cls()
150 return lottie_image.load(image, format)
152 @classmethod
153 def linked(cls, filename):
154 from PIL import Image
155 image = Image.open(filename)
156 lottie_image = cls()
157 lottie_image._id_from_file(filename)
158 lottie_image.path, lottie_image.file_name = os.path.split(filename)
159 lottie_image.path += "/"
160 lottie_image.width = image.width
161 lottie_image.height = image.height
162 return lottie_image
165## @ingroup Lottie
166class Precomp(Asset, Composition):
167 _props = [
168 LottieProp("frame_rate", "fr", float, False),
169 ]
171 def __init__(self, id="", animation=None):
172 super().__init__()
173 ## Precomp ID
174 self.id = id
175 self.animation = animation
176 if animation: 176 ↛ 177line 176 didn't jump to line 177, because the condition on line 176 was never true
177 self.animation.assets.append(self)
178 self.name = None
179 self.frame_rate = None
181 def _on_prepare_layer(self, layer):
182 if self.animation:
183 self.animation.prepare_layer(layer)
185 def set_timing(self, outpoint, inpoint=0, override=True):
186 for layer in self.layers:
187 if override or layer.in_point is None:
188 layer.in_point = inpoint
189 if override or layer.out_point is None:
190 layer.out_point = outpoint
193#ingroup Lottie
194class DataSource(FileAsset):
195 """!
196 External data source, usually a JSON file
197 """
198 _props = [
199 LottieProp("type", "t", int, False),
200 ]
201 type = 3
204#ingroup Lottie
205class Sound(FileAsset):
206 """!
207 External sound
208 """