Initial Commit

This commit is contained in:
OmniLottie
2026-03-01 21:36:54 +08:00
commit a386c803e1
199 changed files with 42253 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
from . import base, core, sif, svg
from .base import importers
__all__ = [
"base", "core", "sif", "svg",
"importers",
]
try:
from . import raster
__all__ += ["raster"]
except ImportError:
pass
+21
View File
@@ -0,0 +1,21 @@
from ..parsers.baseporter import Baseporter, Loader
class ImporterLoader(Loader):
def __init__(self):
super().__init__(__file__, __name__, "import")
@property
def importers(self):
return self.items
def set_options(self, parser):
group = parser.add_argument_group("Generic input options")
super().set_options(parser)
return group
importers = ImporterLoader()
importer = importers.decorator
+7
View File
@@ -0,0 +1,7 @@
from .base import importer
from ..parsers.tgs import parse_tgs
@importer("Lottie JSON / Telegram Sticker", ["json", "tgs"], slug="lottie")
def import_tgs(file, *a, **kw):
return parse_tgs(file, *a, **kw)
+32
View File
@@ -0,0 +1,32 @@
import json
import zipfile
from .base import importer
from ..parsers.baseporter import ExtraOption
from ..parsers.tgs import parse_tgs
from ..objects import Animation, assets
@importer("dotLottie Archive", ["lottie"], [
ExtraOption("id", help="ID of the animation to extract", default=None)
], slug="dotlottie")
def import_dotlottie(file, id=None):
with zipfile.ZipFile(file) as zf:
with zf.open("manifest.json") as manifest:
meta = json.load(manifest)
if id is None:
id = meta["animations"][0]["id"]
info = zf.getinfo("animations/%s.json" % id)
with zf.open(info) as animfile:
an = Animation.load(json.load(animfile))
if an.assets:
for asset in an.assets:
if isinstance(asset, assets.Image) and not asset.is_embedded:
fname = asset.image_path + asset.image
if fname in zf.namelist():
with zf.open(fname) as imgfile:
asset.load(imgfile)
return an
+58
View File
@@ -0,0 +1,58 @@
import zipfile
import warnings
from xml.etree import ElementTree
from .base import importer
from ..parsers.svg.importer import SvgParser
from .. import objects
ns = "{%s}" % "http://www.calligra.org/DTD/krita"
def _ns(string):
return string.format(ns=ns)
def _import_layers(zf, animation, xml_parent, svg_parser, parent):
for xml_layer in xml_parent.findall(_ns("./{ns}layers/{ns}layer")):
nodetype = xml_layer.attrib["nodetype"]
if nodetype == "grouplayer":
layer = animation.add_layer(objects.NullLayer())
_import_layers(zf, animation, xml_layer, svg_parser, layer)
elif nodetype == "shapelayer":
filename = "%s/layers/%s.shapelayer/content.svg" % (animation.name, xml_layer.attrib["filename"])
with zf.open(filename) as svg_tree:
layer = svg_parser.etree_to_layer(animation, ElementTree.parse(svg_tree))
else:
warnings.warn("Unsupported krita layer %s" % nodetype)
continue
layer.name = xml_layer.attrib["name"]
if xml_layer.attrib["visible"] == 0:
layer.transform.opacity.value = 0
layer.parent = parent
@importer("Krita", ["kra"])
def import_krita(file):
with zipfile.ZipFile(file) as zf:
with zf.open("maindoc.xml") as main:
main_xml = ElementTree.parse(main)
image = main_xml.find(_ns("./{ns}IMAGE"))
fps = float(main_xml.find(_ns("./{ns}IMAGE/{ns}animation/{ns}framerate")).attrib["value"])
framerange = main_xml.find(_ns("./{ns}IMAGE/{ns}animation/{ns}range")).attrib
animation = objects.Animation(int(framerange["to"]), fps)
animation.in_point = int(framerange["from"])
animation.width = int(image.attrib["width"])
animation.height = int(image.attrib["height"])
animation.name = image.attrib["name"]
parser = SvgParser()
parser.dpi = int(image.attrib["x-res"])
_import_layers(zf, animation, image, parser, None)
return animation
+72
View File
@@ -0,0 +1,72 @@
from .base import importer
from ..parsers.baseporter import ExtraOption
from ..parsers.pixel import (
pixel_to_animation_paths, pixel_to_animation,
raster_to_embedded_assets, raster_to_linked_assets
)
from ..parsers.svg.importer import parse_color
try:
from ..parsers.raster import raster_to_animation
raster = True
except ImportError:
raster = False
@importer("Raster image", ["bmp", "png", "gif", "webp", "tiff"], [
ExtraOption("n_colors", type=int, default=1, help="Number of colors to quantize"),
ExtraOption("palette", type=parse_color, default=[], nargs="+", help="Custom palette"),
ExtraOption(
"mode",
default="embed",
choices=["external", "embed", "pixel", "polygon"] + (["trace"] if raster else []),
help="Vectorization mode:\n" +
" * external : load images as linked assets\n" +
" * embed : load images as embedded assets\n" +
" * pixel : Vectorize the image into rectangles\n" +
" * polygon : Vectorize the image into polygonal shapes\n" +
" Looks the same as pixel, but a single shape per color\n" +
" * trace : (if available) Use potrace to vectorize\n"
),
ExtraOption("frame_delay", type=int, default=4, help="Number of frames to skip between images"),
ExtraOption("framerate", type=int, default=60, help="Frames per second"),
ExtraOption("frame_files", nargs="+", default=[], help="Additional frames to import"),
ExtraOption(
"color_mode",
default="nearest",
choices=["nearest", "exact"],
help="How to quantize colors.\n" +
" * nearest will map each color to the most similar in the palette\n" +
" * exact will only match exact colors"
),
ExtraOption(
"embed_format",
default=None,
help="Format to store images internally when using `embed` mode"
),
])
def import_raster(filenames, n_colors, palette, mode, frame_delay=1,
framerate=60, frame_files=[], color_mode="nearest", embed_format=None):
if not isinstance(filenames, list):
filenames = [filenames]
filenames = filenames + frame_files
if mode == "embed":
return raster_to_embedded_assets(filenames, frame_delay, framerate, embed_format)
elif mode == "external":
return raster_to_linked_assets(filenames, frame_delay, framerate)
elif mode == "trace":
from ..parsers.raster import QuanzationMode
# TODO QuanzationMode for raster
cm = QuanzationMode.Nearest if color_mode == "nearest" else QuanzationMode.Exact
return raster_to_animation(
filenames, n_colors, frame_delay,
framerate=framerate,
palette=palette,
mode=cm
)
elif mode == "polygon":
return pixel_to_animation_paths(filenames, frame_delay, framerate)
else:
return pixel_to_animation(filenames, frame_delay, framerate)
+16
View File
@@ -0,0 +1,16 @@
import json
import tempfile
import subprocess
from .base import importer
from ..objects import Animation
@importer("Python script", ["py"])
def import_python_script(file, *a, **kw):
out = subprocess.check_output(["python", file, "--version"])
if b"python-lottie script" not in out:
raise Exception("Not a valid script")
data = subprocess.check_output(["python", file, "--path", "", "--name", "-", "--format", "json"])
return Animation.load(json.loads(data))
+7
View File
@@ -0,0 +1,7 @@
from .base import importer
from ..parsers.sif import parse_sif_file
@importer("Synfig", ["sif", "sifz"])
def import_sif(file, *a, **kw):
return parse_sif_file(file, *a, **kw)
+16
View File
@@ -0,0 +1,16 @@
from .base import importer
from ..parsers.baseporter import ExtraOption
from ..parsers.svg import parse_svg_file
from ..parsers.tgs import open_maybe_gzipped
@importer("SVG", ["svg", "svgz"], [
ExtraOption(
"layer_frames", type=int, default=0,
help="If greater than 0, treats every layer in the SVG as a different animation frame,\n"
"greater values increase the time each frames lasts for."),
ExtraOption("n_frames", type=int, default=60),
ExtraOption("framerate", type=int, default=60),
])
def import_svg(file, *a, **kw):
return open_maybe_gzipped(file, lambda svgfile: parse_svg_file(svgfile, *a, **kw))