File indexing completed on 2025-01-19 03:59:52
0001 import os 0002 import re 0003 import base64 0004 import mimetypes 0005 from io import BytesIO 0006 from .base import LottieObject, LottieProp, PseudoBool, Index 0007 from .layers import Layer 0008 from .shapes import ShapeElement 0009 from .composition import Composition 0010 0011 0012 ## @ingroup Lottie 0013 class Asset(LottieObject): 0014 @classmethod 0015 def _load_get_class(cls, lottiedict): 0016 if "p" in lottiedict or "u" in lottiedict: 0017 return Image 0018 if "layers" in lottiedict: 0019 return Precomp 0020 0021 0022 ## @ingroup Lottie 0023 class Image(Asset): 0024 """! 0025 External image 0026 0027 @see http://docs.aenhancers.com/sources/filesource/ 0028 """ 0029 _props = [ 0030 LottieProp("height", "h", float, False), 0031 LottieProp("width", "w", float, False), 0032 LottieProp("id", "id", str, False), 0033 LottieProp("image", "p", str, False), 0034 LottieProp("image_path", "u", str, False), 0035 LottieProp("is_embedded", "e", PseudoBool, False), 0036 ] 0037 0038 @staticmethod 0039 def guess_mime(file): 0040 if isinstance(file, str): 0041 filename = file 0042 elif hasattr(file, "name"): 0043 filename = file.name 0044 else: 0045 return "application/octet-stream" 0046 return mimetypes.guess_type(filename) 0047 0048 def __init__(self, id=""): 0049 ## Image Height 0050 self.height = 0 0051 ## Image Width 0052 self.width = 0 0053 ## Image ID 0054 self.id = id 0055 ## Image name 0056 self.image = "" 0057 ## Image path 0058 self.image_path = "" 0059 ## Image data is stored as a data: url 0060 self.is_embedded = False 0061 0062 def load(self, file, format=None): 0063 """! 0064 @param file Filename, file object, or PIL.Image.Image to load 0065 @param format Format to store the image data as 0066 """ 0067 from PIL import Image 0068 0069 if not isinstance(file, Image.Image): 0070 image = Image.open(file) 0071 else: 0072 image = file 0073 0074 self._id_from_file(file) 0075 0076 self.image_path = "" 0077 if format is None: 0078 format = (image.format or "png").lower() 0079 self.width, self.height = image.size 0080 output = BytesIO() 0081 image.save(output, format=format) 0082 self.image = "data:image/%s;base64,%s" % ( 0083 format, 0084 base64.b64encode(output.getvalue()).decode("ascii") 0085 ) 0086 self.is_embedded = True 0087 return self 0088 0089 def _id_from_file(self, file): 0090 if not self.id: 0091 if isinstance(file, str): 0092 self.id = os.path.basename(file) 0093 elif hasattr(file, "name"): 0094 self.id = os.path.basename(file.name) 0095 elif hasattr(file, "filename"): 0096 self.id = os.path.basename(file.filename) 0097 else: 0098 self.id = "image_%s" % id(self) 0099 0100 @classmethod 0101 def embedded(cls, image, format=None): 0102 """! 0103 Create an object from an image file 0104 """ 0105 lottie_image = cls() 0106 return lottie_image.load(image, format) 0107 0108 @classmethod 0109 def linked(cls, filename): 0110 from PIL import Image 0111 image = Image.open(filename) 0112 lottie_image = cls() 0113 lottie_image._id_from_file(filename) 0114 lottie_image.image_path, lottie_image.image = os.path.split(filename) 0115 lottie_image.image_path += "/" 0116 lottie_image.width = image.width 0117 lottie_image.height = image.height 0118 return lottie_image 0119 0120 def image_data(self): 0121 """ 0122 Returns a tuple (format, data) with the contents of the image 0123 0124 `format` is a string like "png", and `data` is just raw binary data. 0125 0126 If it's impossible to fetch this info, returns (None, None) 0127 """ 0128 if self.is_embedded: 0129 m = re.match("data:[^/]+/([^;,]+);base64,(.*)", self.image) 0130 if m: 0131 return m.group(1), base64.b64decode(m.group(2)) 0132 return None, None 0133 path = self.image_path + self.image 0134 if os.path.isfile(path): 0135 with open(path, "rb") as imgfile: 0136 return os.path.splitext(path)[1][1:], imgfile.read() 0137 return None, None 0138 0139 0140 ## @ingroup Lottie 0141 class CharacterData(LottieObject): 0142 """! 0143 Character shapes 0144 """ 0145 _props = [ 0146 LottieProp("shapes", "shapes", ShapeElement, True), 0147 ] 0148 0149 def __init__(self): 0150 self.shapes = [] 0151 0152 0153 ## @ingroup Lottie 0154 class Chars(LottieObject): 0155 """! 0156 Defines character shapes to avoid loading system fonts 0157 """ 0158 _props = [ 0159 LottieProp("character", "ch", str, False), 0160 LottieProp("font_family", "fFamily", str, False), 0161 LottieProp("font_size", "size", float, False), 0162 LottieProp("font_style", "style", str, False), 0163 LottieProp("width", "w", float, False), 0164 LottieProp("data", "data", CharacterData, False), 0165 ] 0166 0167 def __init__(self): 0168 ## Character Value 0169 self.character = "" 0170 ## Character Font Family 0171 self.font_family = "" 0172 ## Character Font Size 0173 self.font_size = 0 0174 ## Character Font Style 0175 self.font_style = "" # Regular 0176 ## Character Width 0177 self.width = 0 0178 ## Character Data 0179 self.data = CharacterData() 0180 0181 @property 0182 def shapes(self): 0183 return self.data.shapes 0184 0185 0186 ## @ingroup Lottie 0187 class Precomp(Asset, Composition): 0188 _props = [ 0189 LottieProp("id", "id", str, False), 0190 ] 0191 0192 def __init__(self, id="", animation=None): 0193 super().__init__() 0194 ## Precomp ID 0195 self.id = id 0196 self.animation = animation 0197 if animation: 0198 self.animation.assets.append(self) 0199 0200 def _on_prepare_layer(self, layer): 0201 if self.animation: 0202 self.animation.prepare_layer(layer) 0203 0204 def set_timing(self, outpoint, inpoint=0, override=True): 0205 for layer in self.layers: 0206 if override or layer.in_point is None: 0207 layer.in_point = inpoint 0208 if override or layer.out_point is None: 0209 layer.out_point = outpoint