Warning, file /graphics/glaxnimate/data/plugins/dotLottie/dotlottie.py was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 # SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0002 # SPDX-License-Identifier: GPL-3.0-or-later
0003 import io
0004 import re
0005 import os
0006 import json
0007 import string
0008 import base64
0009 import zipfile
0010 from PIL import Image
0011 
0012 import glaxnimate
0013 
0014 
0015 def asset_data(asset):
0016     if asset["e"]:
0017         m = re.match("data:[^/]+/([^;,]+);base64,(.*)", asset["p"])
0018         if not m:
0019             return None, None
0020         return m.group(1), base64.b64decode(m.group(2))
0021 
0022     path = os.path.join(asset["u"], asset["p"])
0023     if not os.path.isfile(path):
0024         return None, None
0025 
0026     with open(path, "rb") as imgfile:
0027         return os.path.splitext(path)[1][1:], imgfile.read()
0028 
0029 
0030 def save_dotlottie(window, document, file, fname, import_export, settings):
0031     files = {}
0032 
0033     if settings["append"]:
0034         with zipfile.ZipFile(fname, "r") as zf:
0035             with zf.open("manifest.json") as manifest:
0036                 meta = json.load(manifest)
0037                 meta["custom"].update(document.metadata)
0038 
0039             for name in zf.namelist():
0040                 if name != "manifest.json":
0041                     files[name] = zf.read(name)
0042     else:
0043         meta = {
0044             "generator": "Glaxnimate " + glaxnimate.__version__,
0045             "version": 1.0,
0046             "revision": 1,
0047             "author": str(document.metadata.get("author", "")),
0048             "animations": [],
0049             "custom": document.metadata,
0050             "author": document.info.author,
0051         }
0052 
0053     try:
0054         meta["revision"] = int(document.metadata["revision"])
0055     except (KeyError, ValueError):
0056         pass
0057 
0058     id = settings["id"]
0059     if not id:
0060         if document.main.name:
0061             idok = string.ascii_letters + string.digits + "_-"
0062             id = "".join(filter(lambda x: x in idok, document.main.name.replace(" ", "_")))
0063         if not id:
0064             id = "animation_%s" % len(meta["animations"])
0065 
0066     meta["animations"].append({
0067         "id": id,
0068         "speed": settings["speed"],
0069         "themeColor": settings["theme_color"],
0070         "loop": settings["loop"],
0071     })
0072 
0073     lottie_str = glaxnimate.io.registry["lottie"].save(document)
0074     lottie_dict = json.loads(lottie_str)
0075 
0076     if settings["pack_assets"]:
0077         image_no = 0
0078         for asset in lottie_dict.get("assets", []):
0079             if "e" not in asset or "p" not in asset:
0080                 continue
0081 
0082             ext, data = asset_data(asset)
0083             if not ext:
0084                 continue
0085 
0086             pathname = "images/"
0087             while True:
0088                 basename = "image_%s.%s" % (image_no, ext)
0089                 image_no += 1
0090                 if pathname+basename not in files:
0091                     break
0092             files[pathname+basename] = data
0093             asset["u"] = pathname
0094             asset["p"] = basename
0095             asset["e"] = 0
0096 
0097     files["manifest.json"] = json.dumps(meta)
0098     files["animations/%s.json" % id] = json.dumps(lottie_dict)
0099 
0100     with open(fname, "wb") as fd:
0101         with zipfile.ZipFile(fd, "w") as zf:
0102             for name, data in files.items():
0103                 zf.writestr(name, data)
0104 
0105 
0106 def load_asset(asset, file):
0107     image = Image.open(file)
0108     asset["u"] = ""
0109     asset["p"] = ""
0110     format = (image.format or "png").lower()
0111     asset["w"], asset["h"] = image.size
0112     output = io.BytesIO()
0113     image.save(output, format=format)
0114     asset["p"] = "data:image/%s;base64,%s" % (
0115         format,
0116         base64.b64encode(output.getvalue()).decode("ascii")
0117     )
0118     asset["e"] = 1
0119 
0120 def open_dotlottie(window, document, file, fname, import_export, settings):
0121     file.open("rb")
0122     with zipfile.ZipFile(file, "r") as zf:
0123         with zf.open("manifest.json") as manifest:
0124             meta = json.load(manifest)
0125 
0126         animations = {}
0127         ids = {}
0128         for anim in meta["animations"]:
0129             anim_id = anim["id"]
0130             info = zf.getinfo("animations/%s.json" % anim_id)
0131             with zf.open(info) as animfile:
0132                 lottie = json.load(animfile)
0133 
0134             animations[anim_id] = lottie
0135             ids[lottie.get("nm", anim_id)] = anim_id
0136 
0137         chosen_id = None
0138         if len(meta["animations"]) > 1:
0139             chosen_id = window.choose_option("Animation", ids, "Open dotLottie")
0140         if chosen_id is None:
0141             chosen_id = meta["animations"][0]["id"]
0142 
0143         lottie = animations[chosen_id]
0144 
0145         for asset in lottie.get("assets", []):
0146             # .lottie is very werid on how it handles assets so we can't
0147             # rely on `e` or `p`...
0148             if asset.get("p", "").startswith("data:"):
0149                 continue
0150 
0151             asset_fname = os.path.join(asset["u"], asset["p"])
0152             if asset_fname not in zf.namelist() and "images/" + asset_fname in zf.namelist():
0153                 asset_fname = "images/" + asset_fname
0154 
0155             if asset_fname in zf.namelist():
0156                 with zf.open(asset_fname) as asset_file:
0157                     load_asset(asset, asset_file)
0158 
0159         glaxnimate.io.registry["lottie"].load(document, json.dumps(lottie).encode("utf-8"))
0160 
0161         custom_meta = meta.get("custom", {})
0162         if "author" in meta:
0163             custom_meta["author"] = meta["author"]
0164         if "revision" in meta:
0165             custom_meta["revision"] = meta["revision"]
0166         document.metadata = custom_meta