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