mirror of
https://github.com/OpenVGLab/OmniLottie.git
synced 2026-09-17 07:36:27 +00:00
Initial Commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user