Files
OmniLottie/lottie/exporters/tgs_validator.py
T

199 lines
5.3 KiB
Python
Raw Normal View History

2026-03-01 21:36:54 +08:00
import os
import enum
import json
import inspect
from ..parsers.tgs import parse_tgs
from ..objects.base import ObjectVisitor
from ..objects.animation import Animation
from ..objects import layers
from ..objects import shapes
from ..objects import helpers
class Severity(enum.Enum):
Note = enum.auto()
Warning = enum.auto()
Error = enum.auto()
class TgsError:
def __init__(self, message, target, severity=Severity.Warning):
self.message = message
self.target = target
self.severity = severity
def target_id(self):
if isinstance(self.target, str):
return self.target
if getattr(self.target, "name", ""):
return self.target.name
return self.target.__class__.__name__
def __str__(self):
return "%s: on %s: %s" % (
self.severity.name,
self.target_id(),
self.message
)
class TgsValidator(ObjectVisitor):
def __init__(self, severity=Severity.Note):
self.errors = []
self.severity = severity
def _check(self, expr, message, target, severity=Severity.Warning):
if severity.value >= self.severity.value and not expr:
self.errors.append(TgsError(message, target, severity))
def check_file_size(self, filename):
return self.check_size(os.path.getsize(filename))
def check_size(self, bytes, filename="file"):
size_k = bytes / 1024
self._check(
size_k <= 64,
"Invalid size (%.1fk), should be less than 64k" % size_k,
filename,
Severity.Error
)
def check_file(self, filename):
self.check_file_size(filename)
try:
self(parse_tgs(filename))
except json.decoder.JSONDecodeError as e:
self._check(
False,
"Invalid JSON: %s" % e,
filename,
Severity.Error
)
def visit(self, object):
for cls in inspect.getmro(object.__class__):
callback = "_visit_%s" % cls.__name__.lower()
if hasattr(self, callback):
getattr(self, callback)(object)
def _visit_animation(self, o: Animation):
self._check(
o.frame_rate in {30, 60},
"Invalid framerate %s, should be 30 or 60" % o.frame_rate,
o,
Severity.Error
)
self._check(
o.width == 512,
"Invalid width %s, should be 512" % o.width,
o,
Severity.Error
)
self._check(
o.height == 512,
"Invalid height %s, should be 512" % o.height,
o,
Severity.Error
)
self._check(
(o.out_point-o.in_point) <= 180,
"Too many frames (%s), should be less than 180" % (o.out_point-o.in_point),
o,
Severity.Error
)
def _visit_layer(self, o: layers.Layer):
self._check(
not o.has_masks and not o.masks,
"Masks are not officially supported",
o,
Severity.Note
)
self._check(
not o.effects,
"Effects are not supported",
o,
Severity.Warning
)
self._check(
not o.threedimensional,
"3D layers are not supported",
o,
Severity.Warning
)
self._check(
not isinstance(o, layers.TextLayer),
"Text layers are not supported",
o,
Severity.Warning
)
self._check(
not isinstance(o, layers.ImageLayer),
"Image layers are not supported",
o,
Severity.Warning
)
self._check(
not o.auto_orient,
"Auto-orient layers are not supported",
o,
Severity.Warning
)
self._check(
o.matte_mode in {None, layers.MatteMode.Normal},
"Mattes are not supported",
o,
Severity.Warning
)
def _visit_precomplayer(self, o: layers.PreCompLayer):
self._check(
o.time_remapping is None,
"Time remapping is not supported",
o,
Severity.Warning
)
def _visit_merge(self, o: shapes.Merge):
self._check(
False,
"Merge paths are not supported",
o,
Severity.Warning
)
def _visit_transform(self, o: helpers.Transform):
self._check(
o.skew is None or (
not o.skew.animated and o.skew.value == 0
),
"Skew transforms are not supported",
o,
Severity.Warning
)
def _visit_gradientstroke(self, o: shapes.GradientStroke):
self._check(
False,
"Gradient strokes are not officially supported",
o,
Severity.Note
)
def _visit_star(self, o: shapes.Star):
self._check(
False,
"Star Shapes are not officially supported",
o,
Severity.Note
)
def _visit_repeater(self, o: shapes.Repeater):
self._check(
False,
"Repeaters are not officially supported",
o,
Severity.Note
)