Files
OmniLottie/lottie/objects/assets.py
T
2026-03-01 21:36:54 +08:00

210 lines
5.9 KiB
Python

import os
import re
import base64
import mimetypes
from io import BytesIO
from .base import LottieObject, LottieProp, PseudoBool, Index
from .layers import Layer
from .shapes import ShapeElement
from .composition import Composition
## @ingroup Lottie
class Asset(LottieObject):
@classmethod
def _load_get_class(cls, lottiedict):
if "p" in lottiedict or "u" in lottiedict:
return Image
if "layers" in lottiedict:
return Precomp
## @ingroup Lottie
class Image(Asset):
"""!
External image
@see http://docs.aenhancers.com/sources/filesource/
"""
_props = [
LottieProp("height", "h", float, False),
LottieProp("width", "w", float, False),
LottieProp("id", "id", str, False),
LottieProp("image", "p", str, False),
LottieProp("image_path", "u", str, False),
LottieProp("is_embedded", "e", PseudoBool, False),
]
@staticmethod
def guess_mime(file):
if isinstance(file, str):
filename = file
elif hasattr(file, "name"):
filename = file.name
else:
return "application/octet-stream"
return mimetypes.guess_type(filename)
def __init__(self, id=""):
## Image Height
self.height = 0
## Image Width
self.width = 0
## Image ID
self.id = id
## Image name
self.image = ""
## Image path
self.image_path = ""
## Image data is stored as a data: url
self.is_embedded = False
def load(self, file, format=None):
"""!
@param file Filename, file object, or PIL.Image.Image to load
@param format Format to store the image data as
"""
from PIL import Image
if not isinstance(file, Image.Image):
image = Image.open(file)
else:
image = file
self._id_from_file(file)
self.image_path = ""
if format is None:
format = (image.format or "png").lower()
self.width, self.height = image.size
output = BytesIO()
image.save(output, format=format)
self.image = "data:image/%s;base64,%s" % (
format,
base64.b64encode(output.getvalue()).decode("ascii")
)
self.is_embedded = True
return self
def _id_from_file(self, file):
if not self.id:
if isinstance(file, str):
self.id = os.path.basename(file)
elif hasattr(file, "name"):
self.id = os.path.basename(file.name)
elif hasattr(file, "filename"):
self.id = os.path.basename(file.filename)
else:
self.id = "image_%s" % id(self)
@classmethod
def embedded(cls, image, format=None):
"""!
Create an object from an image file
"""
lottie_image = cls()
return lottie_image.load(image, format)
@classmethod
def linked(cls, filename):
from PIL import Image
image = Image.open(filename)
lottie_image = cls()
lottie_image._id_from_file(filename)
lottie_image.image_path, lottie_image.image = os.path.split(filename)
lottie_image.image_path += "/"
lottie_image.width = image.width
lottie_image.height = image.height
return lottie_image
def image_data(self):
"""
Returns a tuple (format, data) with the contents of the image
`format` is a string like "png", and `data` is just raw binary data.
If it's impossible to fetch this info, returns (None, None)
"""
if self.is_embedded:
m = re.match("data:[^/]+/([^;,]+);base64,(.*)", self.image)
if m:
return m.group(1), base64.b64decode(m.group(2))
return None, None
path = self.image_path + self.image
if os.path.isfile(path):
with open(path, "rb") as imgfile:
return os.path.splitext(path)[1][1:], imgfile.read()
return None, None
## @ingroup Lottie
class CharacterData(LottieObject):
"""!
Character shapes
"""
_props = [
LottieProp("shapes", "shapes", ShapeElement, True),
]
def __init__(self):
self.shapes = []
## @ingroup Lottie
class Chars(LottieObject):
"""!
Defines character shapes to avoid loading system fonts
"""
_props = [
LottieProp("character", "ch", str, False),
LottieProp("font_family", "fFamily", str, False),
LottieProp("font_size", "size", float, False),
LottieProp("font_style", "style", str, False),
LottieProp("width", "w", float, False),
LottieProp("data", "data", CharacterData, False),
]
def __init__(self):
## Character Value
self.character = ""
## Character Font Family
self.font_family = ""
## Character Font Size
self.font_size = 0
## Character Font Style
self.font_style = "" # Regular
## Character Width
self.width = 0
## Character Data
self.data = CharacterData()
@property
def shapes(self):
return self.data.shapes
## @ingroup Lottie
class Precomp(Asset, Composition):
_props = [
LottieProp("id", "id", str, False),
]
def __init__(self, id="", animation=None):
super().__init__()
## Precomp ID
self.id = id
self.animation = animation
if animation:
self.animation.assets.append(self)
def _on_prepare_layer(self, layer):
if self.animation:
self.animation.prepare_layer(layer)
def set_timing(self, outpoint, inpoint=0, override=True):
for layer in self.layers:
if override or layer.in_point is None:
layer.in_point = inpoint
if override or layer.out_point is None:
layer.out_point = outpoint