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

9944 lines
453 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import torch
import re
import json
from typing import Union, List, Dict, Tuple, Optional, Any
import difflib
from .test2_0518 import *
import numpy as np
#from test_0819 import *
class LottieTensor:
# Command type constants (添加新的命令常量)
tokenizer = None
CMD_ANIMATION = 0
CMD_LAYER = 1
CMD_TRANSFORM = 2
CMD_POSITION = 3
CMD_KEYFRAME = 4
CMD_POSITION_END = 5
CMD_SCALE = 6
CMD_SCALE_END = 7
CMD_ROTATION = 8
CMD_OPACITY = 9
CMD_OPACITY_END = 10
CMD_ANCHOR = 11
CMD_GROUP = 12
CMD_GROUP_END = 13
CMD_TRANSFORM_SHAPE = 14
CMD_PATH = 15
CMD_PATH_END = 16
CMD_POINT = 17
CMD_FILL = 18
CMD_GRADIENT_FILL = 19
CMD_GRADIENT_FILL_END = 20
CMD_START_POINT = 21
CMD_END_POINT = 22
CMD_GRADIENT_TYPE = 23
CMD_HIGHLIGHT_LENGTH = 24
CMD_HIGHLIGHT_ANGLE = 25
CMD_TRANSFORM_END = 26
CMD_LAYER_END = 27
CMD_PAD = 28
CMD_EOS = 29
CMD_SOS = 30
CMD_RECT = 31
CMD_RECT_END = 32
CMD_SIZE = 33
CMD_ROUNDED = 34
CMD_ELLIPSE = 35
CMD_ELLIPSE_END = 36
CMD_STROKE = 37
CMD_SKEW = 38
CMD_SKEW_AXIS = 39
CMD_ASSET = 40
CMD_ASSET_END = 41
CMD_PARENT = 42
CMD_NULL_LAYER = 43
CMD_NULL_LAYER_END = 44
CMD_PRECOMP_LAYER = 45
CMD_PRECOMP_LAYER_END = 46
CMD_REFERENCE_ID = 47
CMD_DIMENSIONS = 48
CMD_ROTATION_END = 49
CMD_STAR = 50
CMD_STAR_END = 51
CMD_INNER_RADIUS = 52
CMD_OUTER_RADIUS = 53
CMD_INNER_ROUNDNESS = 54
CMD_OUTER_ROUNDNESS = 55
CMD_POINTS = 56
CMD_STAR_ROTATION = 57
CMD_TRIM = 58
CMD_TRIM_END = 59
CMD_START = 60
CMD_END = 61
CMD_OFFSET = 62
CMD_MULTIPLE = 63
CMD_REPEATER = 64
CMD_REPEATER_END = 65
CMD_COPIES = 66
CMD_REPEATER_OFFSET = 67
CMD_COMPOSITE = 68
CMD_REPEATER_TRANSFORM = 69
CMD_REPEATER_TRANSFORM_END = 70
CMD_GRADIENT_STROKE = 71
CMD_GRADIENT_STROKE_END = 72
CMD_WIDTH = 73
CMD_LINE_CAP = 74
CMD_LINE_JOIN = 75
CMD_MITER_LIMIT = 76
CMD_MERGE = 77
CMD_MERGE_END = 78
CMD_MERGE_MODE = 79
CMD_ROUNDED_CORNERS = 80
CMD_ROUNDED_CORNERS_END = 81
CMD_RADIUS = 82
CMD_TWIST = 83
CMD_TWIST_END = 84
CMD_ANGLE = 85
CMD_CENTER = 86
CMD_BEZIER = 87
CMD_BEZIER_END = 88
CMD_TEXT_LAYER = 89
CMD_TEXT_LAYER_END = 90
CMD_TEXT_DATA = 91
CMD_TEXT_DATA_END = 92
CMD_DOCUMENT = 93
CMD_SOLID_LAYER = 94
CMD_SOLID_LAYER_END = 95
CMD_POSITION_X = 96
CMD_POSITION_Y = 97
CMD_POSITION_Z = 98
CMD_POSITION_X_END = 99
CMD_POSITION_Y_END = 100
CMD_POSITION_Z_END = 101
CMD_SCALE_X = 102
CMD_SCALE_Y = 103
CMD_SCALE_Z = 104
CMD_SCALE_X_END = 105
CMD_SCALE_Y_END = 106
CMD_SCALE_Z_END = 107
CMD_ROTATION_X = 108
CMD_ROTATION_Y = 109
CMD_ROTATION_Z = 110
CMD_ROTATION_X_END = 111
CMD_ROTATION_Y_END = 112
CMD_ROTATION_Z_END = 113
CMD_EFFECTS = 114
CMD_EFFECTS_END = 115
CMD_EFFECT = 116
CMD_EFFECT_END = 117
CMD_HAS_MASK = 118
CMD_MASKS_PROPERTIES = 119
CMD_CT = 120
CMD_EF = 121
CMD_TT = 122
CMD_TP = 123
CMD_TD = 124
CMD_HD = 125
CMD_CL = 126
CMD_LN = 127
CMD_AO = 128
CMD_ANCHOR_END = 129
CMD_OPACITY_FILL = 130
CMD_FILL_RULE = 131
CMD_COLOR_DIM = 132
CMD_DDD = 133
CMD_MARKERS = 134
CMD_PROPS = 135
CMD_ORIGINAL_COLORS = 136
CMD_COLOR_POINTS = 137
CMD_COLORS = 138
CMD_ML2 = 139
CMD_ML2_IX = 140
CMD_OFFSET_IX = 141
CMD_TR_P_IX = 142
CMD_TR_A_IX = 143
CMD_TR_SCALE = 144
CMD_TR_S_IX = 145
CMD_TR_R_IX = 146
CMD_TR_SO_IX = 147
CMD_TR_EO_IX = 148
CMD_KEYFRAME_END = 149
CMD_POSITION_EXPR = 150
CMD_SCALE_EXPR = 151
CMD_ROTATION_EXPR = 152
CMD_WIDTH_KEYFRAME = 153 # 新增
CMD_WIDTH_ANIMATED_END = 154 # 新增
CMD_FONTS = 155
CMD_FONTS_END = 156
CMD_FONT = 157
CMD_CHARS = 158
CMD_CHARS_END = 159
CMD_CHAR = 160
CMD_CHAR_END = 161
CMD_CHAR_SHAPES = 162
CMD_CHAR_SHAPES_END = 163
CMD_TEXT_KEYFRAMES = 164
CMD_TEXT_KEYFRAMES_END = 165
CMD_TEXT_KEYFRAME = 166
CMD_TEXT_DOC = 167
CMD_TEXT_DOC_END = 168
CMD_FONT_SIZE = 169
CMD_FONT_FAMILY = 170
CMD_TEXT = 171
CMD_CA = 172
CMD_JUSTIFY = 173
CMD_TRACKING = 174
CMD_LINE_HEIGHT = 175
CMD_LETTER_SPACING = 176
CMD_FILL_COLOR = 177
CMD_MORE_OPTIONS = 178
CMD_MORE_OPTIONS_END = 179
CMD_G = 180
CMD_ALIGNMENT = 181
CMD_ALIGNMENT_K = 182
CMD_ALIGNMENT_IX = 183
CMD_DROPDOWN = 184
CMD_IGNORED = 185
CMD_SLIDER = 186
CMD_COLOR = 187
CMD_OPACITY_ANIMATED = 188
CMD_OPACITY_KEYFRAME = 189
CMD_MASKS_PROPERTIES_END = 190
CMD_MASK = 191
CMD_MASK_END = 192
CMD_MASK_PT = 193
CMD_MASK_PT_END = 194
CMD_MASK_PT_K = 195
CMD_MASK_PT_K_END = 196
CMD_MASK_PT_K_I = 197
CMD_MASK_PT_K_O = 198
CMD_MASK_PT_K_V = 199
CMD_MASK_O = 200
CMD_MASK_X = 201
CMD_TM = 202
CMD_TM_END = 203
CMD_MASK_PT_K_ARRAY = 204
CMD_MASK_PT_K_ARRAY_END = 205
CMD_MASK_PT_KEYFRAME = 206
CMD_MASK_PT_KEYFRAME_END = 207
CMD_MASK_PT_KF_I = 208
CMD_MASK_PT_KF_O = 209
CMD_MASK_PT_KF_S = 210
CMD_MASK_PT_KF_S_END = 211
CMD_MASK_PT_KF_SHAPE = 212
CMD_MASK_PT_KF_SHAPE_END = 213
CMD_MASK_PT_KF_SHAPE_I = 214
CMD_MASK_PT_KF_SHAPE_O = 215
CMD_MASK_PT_KF_SHAPE_V = 216
CMD_VALUE = 217
CMD_VALUE_END = 218
CMD_TR_POSITION = 219
CMD_TR_ANCHOR = 220
CMD_TR_ROTATION = 221
CMD_TR_START_OPACITY = 222
CMD_TR_END_OPACITY = 223
CMD_ZIG_ZAG = 224
CMD_ZIG_ZAG_END = 225
CMD_FREQUENCY = 226
CMD_AMPLITUDE = 227
CMD_POINT_TYPE = 228
CMD_ANIMATORS = 229
CMD_ANIMATORS_END = 230
CMD_ANIMATOR = 231
CMD_ANIMATOR_END = 232
CMD_RANGE_SELECTOR = 233
CMD_RANGE_SELECTOR_END = 234
CMD_RANGE_START = 235
CMD_RANGE_START_END = 236
CMD_RANGE_START_KEYFRAME = 237
CMD_AMOUNT = 238
CMD_MAX_EASE = 239
CMD_MIN_EASE = 240
CMD_ANIMATOR_PROPERTIES = 241
CMD_ANIMATOR_PROPERTIES_END = 242
CMD_OPACITY_ANIMATED_END = 243
CMD_MASK_PT_K_C = 244
CMD_RANGE_END = 245
CMD_RANGE_END_END = 246
CMD_RANGE_END_KEYFRAME = 247
CMD_END_END = 248
CMD_START_END = 249
CMD_OFFSET_END = 250
CMD_POINTS_STAR = 251
CMD_RANGE_OFFSET = 252
CMD_RANGE_OFFSET_END = 253
CMD_RANGE_OFFSET_KEYFRAME = 254
CMD_S_M = 255
CMD_OPACITY_ANIMATORS = 256
CMD_SCALE_ANIMATORS = 257
CMD_SCALE_ANIMATORS_END = 258
CMD_ROTATION_ANIMATORS = 259
CMD_ROTATION_ANIMATORS_END = 260
CMD_POSITION_ANIMATORS = 261
CMD_POSITION_ANIMATORS_END = 262
CMD_TRACKING_ANIMATORS = 263
CMD_OPACITY_ANIMATORS_END = 264
CMD_COLOR_KEYFRAME = 265 # Add this constant
CMD_COLOR_ANIMATED_END = 266
CMD_DASHES = 267
CMD_DASHES_END = 268
CMD_DASH = 269
CMD_DASH_OFFSET = 270
CMD_LAYER_EFFECT = 271
CMD_NO_VALUE = 272
CMD_WIDTH_ANIMATED = 273 # Add this if it doesn't exist
CMD_SIZE_END = 274
CMD_RECT_SIZE = 275 # Add this new constant
CMD_ELLIPSE_SIZE = 276
CMD_RECT_ROUNDED = 277 # Add this new constant for animated rect_rounded
CMD_RECT_ROUNDED_END = 278
CMD_DASH_ANIMATED = 279 # New constant
CMD_DASH_KEYFRAME = 280 # New constant
CMD_DASH_ANIMATED_END = 281 # New constant
# Command names mapped to their numeric constants
COMMANDS = [
"animation", # 0
"layer", # 1
"transform", # 2
"position", # 3
"keyframe", # 4
"/position", # 5
"scale", # 6
"/scale", # 7
"rotation", # 8
"opacity", # 9
"/opacity", # 10
"anchor", # 11
"group", # 12
"/group", # 13
'"TransformShape"', # 14
"path", # 15
"/path", # 16
"point", # 17
"fill", # 18
"gradient_fill", # 19
"/gradient_fill", # 20
"start_point", # 21
"end_point", # 22
"gradient_type", # 23
"highlight_length", # 24
"highlight_angle", # 25
"/transform", # 26
"/layer", # 27
"PAD", # 28
"EOS", # 29
"SOS", # 30
"rect", # 31
"/rect", # 32
"size", # 33
"rounded", # 34
"ellipse", # 35
"/ellipse", # 36
"stroke", # 37
"skew", # 38
"skew_axis", # 39
"asset", # 40
"/asset", # 41
"parent", # 42
"null_layer", # 43
"/null_layer", # 44
"precomp_layer", # 45
"/precomp_layer", # 46
"reference_id", # 47
"dimensions", # 48
"/rotation", # 49
"star", # 50
"/star", # 51
"inner_radius", # 52
"outer_radius", # 53
"inner_roundness", # 54
"outer_roundness", # 55
"points", # 56
"star_rotation", # 57
"trim", # 58
"/trim", # 59
"start", # 60
"end", # 61
"offset", # 62
"multiple", # 63
"repeater", # 64
"/repeater", # 65
"copies", # 66
"repeater_offset", # 67
"composite", # 68
"repeater_transform", # 69
"/repeater_transform", # 70
"gradient_stroke", # 71
"/gradient_stroke", # 72
"width", # 73
"line_cap", # 74
"line_join", # 75
"miter_limit", # 76
"merge", # 77
"/merge", # 78
"merge_mode", # 79
"rounded_corners", # 80
"/rounded_corners", # 81
"radius", # 82
"twist", # 83
"/twist", # 84
"angle", # 85
"center", # 86
"bezier", # 87
"/bezier", # 88
"text_layer", # 89
"/text_layer", # 90
"text_data", # 91
"/text_data", # 92
"document", # 93
"solid_layer", # 94
"/solid_layer", # 95
"position_x", # 96
"position_y", # 97
"position_z", # 98
"/position_x", # 99
"/position_y", # 100
"/position_z", # 101
"scale_x", # 102
"scale_y", # 103
"scale_z", # 104
"/scale_x", # 105
"/scale_y", # 106
"/scale_z", # 107
"rotation_x", # 108
"rotation_y", # 109
"rotation_z", # 110
"/rotation_x", # 111
"/rotation_y", # 112
"/rotation_z", # 113
"effects", # 114
"/effects", # 115
"effect", # 116
"/effect", # 117
"hasMask", # 118
"masksProperties", # 119
"ct", # 120
"ef", # 121
"tt", # 122
"tp", # 123
"td", # 124
"hd", # 125
"cl", # 126
"ln", # 127
"ao", # 128
"/anchor", # 129
"opacity_fill", # 130
"fill_rule", # 131
"color_dim", # 132
"ddd", # 133
"markers", # 134
"props", # 135
"original_colors", # 136
"color_points", # 137
"colors", # 138
"ml2", # 139
"ml2_ix", # 140
"offset_ix", # 141
"tr_p_ix", # 142
"tr_a_ix", # 143
"tr_scale", # 144
"tr_s_ix", # 145
"tr_r_ix", # 146
"tr_so_ix", # 147
"tr_eo_ix", # 148
"/keyframe", # 149
"position_expr", # 150
"scale_expr", # 151
"rotation_expr", # 152
"width_keyframe", # 153 # 新增
"/width_animated", # 154 # 新增
"fonts", # 155
"/fonts", # 156
"font", # 157
"chars", # 158
"/chars", # 159
"char", # 160
"/char", #161
"char_shapes", # 162
"/char_shapes", # 163
"text_keyframes", # 164
"/text_keyframes", # 165
"text_keyframe", # 166
"text_doc", # 167
"/text_doc", # 168
"font_size", # 169
"font_family", # 170
"text", # 171
"ca", # 172
"justify", # 173
"tracking_animators", # 174
"line_height", # 175
"letter_spacing", # 176
"fill_color", # 177
"more_options", # 178
"/more_options", # 179
"g", # 180
"alignment", # 181
"alignment_k", # 182
"alignment_ix", # 183
"dropdown", # 184
"ignored", # 185
"slider", # 186
"color", # 187
"opacity_animated", # 188
"opacity_keyframe", # 189
"/masksProperties", # 190
"mask", # 191
"/mask", # 192
"mask_pt", # 193
"/mask_pt", # 194
"mask_pt_k", # 195
"/mask_pt_k", # 196
"mask_pt_k_i", # 197
"mask_pt_k_o", # 198
"mask_pt_k_v", # 199
"mask_o", # 200
"mask_x", # 201
"tm", # 202
"/tm", # 203
"mask_pt_k_array", # 204
"/mask_pt_k_array", # 205
"mask_pt_keyframe", # 206
"/mask_pt_keyframe", # 207
"mask_pt_kf_i", # 208
"mask_pt_kf_o", # 209
"mask_pt_kf_s", # 210
"/mask_pt_kf_s", # 211
"mask_pt_kf_shape", # 212
"/mask_pt_kf_shape", # 213
"mask_pt_kf_shape_i", # 214
"mask_pt_kf_shape_o", # 215
"mask_pt_kf_shape_v", # 216
"value", # 217
"/value", # 218
"tr_position", # 219
"tr_anchor", # 220
"tr_rotation", # 221
"tr_start_opacity", # 222
"tr_end_opacity", # 223
"zig_zag", # 224
"/zig_zag", # 225
"frequency", # 226
"amplitude", # 227
"point_type", # 228
"animators", # 229
"/animators", # 230
"animator", # 231
"/animator", # 232
"range_selector", # 233
"/range_selector", # 234
"range_start", # 235
"/range_start", # 236
"range_start_keyframe", # 237
"amount", # 238
"max_ease", # 239
"min_ease", # 240
"animator_properties", # 241
"/animator_properties", # 242
"/opacity_animated", # 243
"mask_pt_k_c", #244
"range_end", # 245
"/range_end", # 246
"range_end_keyframe", # 247
"/end", #248
"/start" , # 249
"/offset" , # 250
"points_star", #251
"range_offset", # 252
"/range_offset", # 253
"range_offset_keyframe", # 254
"s_m", # 255
"opacity_animators", # 256
"scale_animators", # 257
"/scale_animators", # 258
"rotation_animators", # 259
"/rotation_animators", # 260
"position_animators", # 261
"/position_animators", # 262
"tracking_animators", # 263
"/opacity_animators", #264
"color_keyframe", # 265
"/color_animated", #266
"dashes", # 267
"/dashes", # 268
"dash", # 269
"dash_offset", # 270
"layer_effect", # 271
"no_value", #272
"width_animated" , #273
"/size", #274
"rect_size", # 275 # Add this new command
"ellipse_size", # 276
"rect_rounded", # 277
"/rounded", #278
"dash_animated", # 279 # Add this
"dash_keyframe", # 280 # Add this
"/dash_animated", # 281 # Add this
]
# Command to index mapping
COMMAND_TO_IDX = {cmd: idx for idx, cmd in enumerate(COMMANDS)}
_OFFSET_CACHE = {}
# Parameter indices for each command type (添加新的Index定义)
class Index:
# Animation parameters
class Animation:
FR = 0
IP = 1
OP = 2
W = 3
H = 4
DDD = 5
class Layer:
INDEX = 0
IN_POINT = 1
OUT_POINT = 2
START_TIME = 3
DDD = 4
HD = 5
HAS_MASK = 6
AO = 7
TT = 8
TP = 9
TD = 10
CT = 11
CP = 12
class Value:
VALUE = 0
class Transform:
ANIMATED = 0
X = 1
Y = 2
Z = 3
class Keyframe:
T = 0
S1 = 1
S2 = 2
S3 = 3
I_X = 4 # 第一个值,或单值情况
I_Y = 5 # 第一个值,或单值情况
O_X = 6 # 第一个值,或单值情况
O_Y = 7 # 第一个值,或单值情况
TO1 = 8
TO2 = 9
TO3 = 10
TI1 = 11
TI2 = 12
TI3 = 13
# Multi-dimensional easing (for scale, position, anchor)
I_X2 = 14
I_X3 = 15
I_Y2 = 16
I_Y3 = 17
O_X2 = 18
O_X3 = 19
O_Y2 = 20
O_Y3 = 21
H_FLAG = 22
E1 = 23
E2 = 24
E3 = 25
class Tm:
A = 0
#IX = 1
class WidthKeyframe: # 新增
T = 0
S = 1
I_X = 2
I_Y = 3
O_X = 4
O_Y = 5
class Path:
IX = 0
IND = 1
KS_IX = 2
CLOSED = 3
HD = 4
ANIMATED = 5
class Point:
X = 0
Y = 1
IN_X = 2
IN_Y = 3
OUT_X = 4
OUT_Y = 5
class Fill:
R = 0
G = 1
B = 2
COLOR_DIM = 3
HAS_C_A = 4
HAS_C_IX = 5
C_IX = 6
BM = 7
FILL_RULE = 8
OPACITY = 9
COLOR_ANIMATED = 10 # New
OPACITY_ANIMATED = 11 # New
HAS_O_A = 12 # New
HAS_O_IX = 13 # New
O_IX = 14 # New
class TransformShape:
POSITION_X = 0
POSITION_Y = 1
SCALE_X = 2
SCALE_Y = 3
ROTATION = 4
OPACITY = 5
ANCHOR_X = 6
ANCHOR_Y = 7
SKEW = 8
SKEW_AXIS = 9
HD = 10
class Stroke:
R = 0
G = 1
B = 2
COLOR_DIM = 3
HAS_C_A = 4
HAS_C_IX = 5
C_IX = 6
BM = 7
LC = 8
LJ = 9
ML = 10
#WIDTH = 11
#OPACITY = 12
WIDTH_ANIMATED = 11 # 新增
COLOR_ANIMATED = 12 # Add this
A = 13 # Add alpha channel support
class Bezier:
CLOSED = 0
class Group:
IX = 0
CIX = 1
BM = 2
HD = 3
NP = 4
class Star:
D = 0
SY = 1
class StarValue: # 新增用于 star 的子命令
VALUE = 0
class Trim:
IX = 0
START = 1
END = 2
OFFSET = 3
MULTIPLE = 4
class TrimValue:
VALUE = 0
ANIMATED = 1
IX = 2
class Repeater:
IX = 0
COPIES = 1
REPEATER_OFFSET = 2
COMPOSITE = 3
TR_P_IX = 4
TR_A_IX = 5
TR_SCALE = 6
TR_S_IX = 7
TR_R_IX = 8
TR_SO_IX = 9
TR_EO_IX = 10
class Asset:
#ID = 0
FR = 0
ID_TOKEN_0 = 1
ID_TOKEN_1 = 2
ID_TOKEN_2 = 3
ID_TOKEN_3 = 4
ID_TOKEN_4 = 5
ID_TOKEN_5 = 6
ID_TOKEN_6 = 7
ID_TOKEN_7 = 8
ID_TOKEN_8 = 9
ID_TOKEN_9 = 10
ID_TOKEN_COUNT = 11 # Store count of tokens
class Rect:
HD = 0
D = 1
POSITION_X = 2
POSITION_Y = 3
SIZE_X = 4
SIZE_Y = 5
ROUNDED = 6
IX = 7
class Ellipse:
POSITION_X = 0
POSITION_Y = 1
SIZE_X = 2
SIZE_Y = 3
class SingleValue:
VALUE = 0
IX = 1
ANIMATED = 2
class TwoValues:
VALUE1 = 0
VALUE2 = 1
IX = 2
class ThreeValues:
VALUE1 = 0
VALUE2 = 1
VALUE3 = 2
class NullLayer:
INDEX = 0
IN_POINT = 1
OUT_POINT = 2
START_TIME = 3
CT = 4
#DDD = 5
HD = 5
HAS_MASK = 6
AO = 7
TT = 8
TP = 9
TD = 10
CP = 11
class PrecompLayer:
INDEX = 0
IN_POINT = 1
OUT_POINT = 2
START_TIME = 3
W = 4
H = 5
CT = 6 # 添加CT参数
HAS_MASK = 7
AO = 8
TT = 9
TP = 10
TD = 11
DDD =12
HD = 13
CP = 14
class SolidLayer:
INDEX = 0
IN_POINT = 1
OUT_POINT = 2
START_TIME = 3
WIDTH = 4
HEIGHT = 5
HAS_MASK = 6
COLOR_R = 7
COLOR_G = 8
COLOR_B = 9
COLOR_A = 10
class Parent:
PARENT_INDEX= 0
class ReferenceId: # 新增
ID_TOKEN_0 = 0
ID_TOKEN_1 = 1
ID_TOKEN_2 = 2
ID_TOKEN_3 = 3
ID_TOKEN_4 = 4
ID_TOKEN_5 = 5
ID_TOKEN_6 = 6
ID_TOKEN_7 = 7
ID_TOKEN_8 = 8
ID_TOKEN_9 = 9
ID_TOKEN_COUNT = 10 # Store count of tokens
class Dimensions: # 新增
WIDTH = 0
HEIGHT = 1
class Font:
ASCENT = 0
FAMILY_TOKEN_0 = 1
FAMILY_TOKEN_1 = 2
FAMILY_TOKEN_2 = 3
FAMILY_TOKEN_3 = 4
FAMILY_TOKEN_4 = 5
FAMILY_TOKEN_5 = 6
FAMILY_TOKEN_6 = 7
FAMILY_TOKEN_7 = 8
FAMILY_TOKEN_8 = 9
FAMILY_TOKEN_9 = 10
FAMILY_TOKEN_COUNT = 11
# Reserve slots for style tokens
STYLE_TOKEN_0 = 12
STYLE_TOKEN_1 = 13
STYLE_TOKEN_2 = 14
STYLE_TOKEN_3 = 15
STYLE_TOKEN_4 = 16
STYLE_TOKEN_5 = 17
STYLE_TOKEN_6 = 18
STYLE_TOKEN_7 = 19
STYLE_TOKEN_8 = 20
STYLE_TOKEN_9 = 21
STYLE_TOKEN_COUNT = 22
class Char:
SIZE = 0
W = 1
CH_TOKEN_0 = 2
CH_TOKEN_1 = 3
CH_TOKEN_2 = 4
CH_TOKEN_3 = 5
CH_TOKEN_4 = 6
CH_TOKEN_5 = 7
CH_TOKEN_6 = 8
CH_TOKEN_7 = 9
CH_TOKEN_8 = 10
CH_TOKEN_9 = 11
CH_TOKEN_COUNT = 12
# Reserve slots for style tokens
STYLE_TOKEN_0 = 13
STYLE_TOKEN_1 = 14
STYLE_TOKEN_2 = 15
STYLE_TOKEN_3 = 16
STYLE_TOKEN_4 = 17
STYLE_TOKEN_5 = 18
STYLE_TOKEN_6 = 19
STYLE_TOKEN_7 = 20
STYLE_TOKEN_8 = 21
STYLE_TOKEN_9 = 22
STYLE_TOKEN_COUNT = 23
# Reserve slots for family tokens
FAMILY_TOKEN_0 = 24
FAMILY_TOKEN_1 = 25
FAMILY_TOKEN_2 = 26
FAMILY_TOKEN_3 = 27
FAMILY_TOKEN_4 = 28
FAMILY_TOKEN_5 = 29
FAMILY_TOKEN_6 = 30
FAMILY_TOKEN_7 = 31
FAMILY_TOKEN_8 = 32
FAMILY_TOKEN_9 = 33
FAMILY_TOKEN_COUNT = 34
class TextLayer:
INDEX = 0
IN_POINT = 1
OUT_POINT = 2
START_TIME = 3
HAS_MASK = 4 # 新增
class TextKeyframe:
T = 0
STROKE_WIDTH = 1
OFFSET = 2
WRAP_POSITION_X = 3
WRAP_POSITION_Y = 4
WRAP_SIZE_X = 5
WRAP_SIZE_Y = 6
# Add numeric fields instead of string storage
FONT_SIZE = 7
CA = 8
JUSTIFY = 9
TRACKING = 10
LINE_HEIGHT = 11
LETTER_SPACING = 12
FILL_COLOR_R = 13
FILL_COLOR_G = 14
FILL_COLOR_B = 15
STROKE_COLOR_R = 16
STROKE_COLOR_G = 17
STROKE_COLOR_B = 18
HAS_STROKE_COLOR = 19 # Flag to indicate if stroke_color exists
FONT_FAMILY_TOKENS_START = 20 # Store up to 10 tokens for font_family
TEXT_TOKENS_START = 30 # Store up to 15 tokens for text
FONT_FAMILY_TOKEN_COUNT = 45 # Store the count of font_family tokens
TEXT_TOKEN_COUNT = 46 # Store the count of text tokens
class MoreOptions:
G = 0
ALIGNMENT_A = 1
ALIGNMENT_K1 = 2
ALIGNMENT_K2 = 3
ALIGNMENT_IX = 4
class OriginalColors:
# Support up to 18 color values
COLOR_0 = 0
COLOR_1 = 1
COLOR_2 = 2
COLOR_3 = 3
COLOR_4 = 4
COLOR_5 = 5
COLOR_6 = 6
COLOR_7 = 7
COLOR_8 = 8
COLOR_9 = 9
COLOR_10 = 10
COLOR_11 = 11
COLOR_12 = 12
COLOR_13 = 13
COLOR_14 = 14
COLOR_15 = 15
COLOR_16 = 16
COLOR_17 = 17
COLOR_18 = 18 # Added
COLOR_19 = 19 # Added
COLOR_20 = 20 # Added
COLOR_21 = 21 # Added
COLOR_22 = 22 # Added
COLOR_23 = 23 # Added
COLOR_24 = 24 # Added
COLOR_25 = 25 # Added
COLOR_26 = 26 # Added
COLOR_27 = 27 # Added
COLOR_28 = 28 # Added
COLOR_29 = 29 # Added
COLOR_30 = 30 # Added
COLOR_31 = 31 # Added
COLOR_32 = 32 # Added
COLOR_33 = 33 # Added
COLOR_34 = 34 # Added
COLOR_35 = 35 # Added
COLOR_36 = 36 # Added
COLOR_37 = 37 # Added
COLOR_38 = 38 # Added
COLOR_39 = 39 # Added
COLOR_40 = 40 # Added
COLOR_41 = 41 # Added
COLOR_42 = 42 # Added
COLOR_43 = 43 # Added
COLOR_44 = 44 # Added
COLOR_45 = 45 # Added
COLOR_46 = 46 # Added
COUNT = 47 # Store the count of colors
class FontSize:
SIZE = 0
class Text:
TEXT_TOKEN_0 = 0
TEXT_TOKEN_1 = 1
TEXT_TOKEN_2 = 2
TEXT_TOKEN_3 = 3
TEXT_TOKEN_4 = 4
TEXT_TOKEN_5 = 5
TEXT_TOKEN_6 = 6
TEXT_TOKEN_7 = 7
TEXT_TOKEN_8 = 8
TEXT_TOKEN_9 = 9
TEXT_TOKEN_COUNT = 10
class Ca:
VALUE = 0
class Justify:
VALUE = 0
class Tracking:
VALUE = 0
class LineHeight:
VALUE = 0
class LetterSpacing:
VALUE = 0
class FillColor:
R = 0
G = 1
B = 2
class G:
VALUE = 0
class Alignment:
A = 0
class AlignmentK:
VALUE1 = 0
VALUE2 = 1
class AlignmentIx:
VALUE = 0
class GradientFill:
OPACITY = 0
FILL_RULE = 1
START_POINT_X = 2
START_POINT_Y = 3
END_POINT_X = 4
END_POINT_Y = 5
GRADIENT_TYPE = 6
HIGHLIGHT_LENGTH = 7
HIGHLIGHT_ANGLE = 8
COLOR_POINTS = 9
# Original colors (up to 12 values for RGBA * 3 color stops)
ORIGINAL_COLOR_0 = 10
ORIGINAL_COLOR_1 = 11
ORIGINAL_COLOR_2 = 12
ORIGINAL_COLOR_3 = 13
ORIGINAL_COLOR_4 = 14
ORIGINAL_COLOR_5 = 15
ORIGINAL_COLOR_6 = 16
ORIGINAL_COLOR_7 = 17
ORIGINAL_COLOR_8 = 18
ORIGINAL_COLOR_9 = 19
ORIGINAL_COLOR_10 = 20
ORIGINAL_COLOR_11 = 21
ORIGINAL_COLOR_12 = 22 # Added
ORIGINAL_COLOR_13 = 23 # Added
ORIGINAL_COLOR_14 = 24 # Added
ORIGINAL_COLOR_15 = 25 # Added
ORIGINAL_COLOR_16 = 26 # Added
ORIGINAL_COLOR_17 = 27 # Added
ORIGINAL_COLOR_18 = 28 # Added
ORIGINAL_COLOR_19 = 29 # Added
ORIGINAL_COLOR_20 = 30 # Added
ORIGINAL_COLOR_21 = 31 # Added
ORIGINAL_COLOR_22 = 32 # Added
ORIGINAL_COLOR_23 = 33 # Added
class GradientStroke:
OPACITY = 0
WIDTH = 1
LINE_CAP = 2
LINE_JOIN = 3
MITER_LIMIT = 4
ML2 = 5
ML2_IX = 6
START_POINT_X = 7
START_POINT_Y = 8
END_POINT_X = 9
END_POINT_Y = 10
GRADIENT_TYPE = 11
HIGHLIGHT_LENGTH = 12
HIGHLIGHT_ANGLE = 13
COLOR_POINTS = 14
# Original colors (up to 18 values for RGBA * 4.5 color stops)
ORIGINAL_COLOR_0 = 15
ORIGINAL_COLOR_1 = 16
ORIGINAL_COLOR_2 = 17
ORIGINAL_COLOR_3 = 18
ORIGINAL_COLOR_4 = 19
ORIGINAL_COLOR_5 = 20
ORIGINAL_COLOR_6 = 21
ORIGINAL_COLOR_7 = 22
ORIGINAL_COLOR_8 = 23
ORIGINAL_COLOR_9 = 24
ORIGINAL_COLOR_10 = 25
ORIGINAL_COLOR_11 = 26
ORIGINAL_COLOR_12 = 27
ORIGINAL_COLOR_13 = 28
ORIGINAL_COLOR_14 = 29
ORIGINAL_COLOR_15 = 30
ORIGINAL_COLOR_16 = 31
ORIGINAL_COLOR_17 = 32
ORIGINAL_COLOR_18 = 33 # Added
ORIGINAL_COLOR_19 = 34 # Added
ORIGINAL_COLOR_20 = 35 # Added
ORIGINAL_COLOR_21 = 36 # Added
ORIGINAL_COLOR_22 = 37 # Added
ORIGINAL_COLOR_23 = 38 # Added
class StartPointCmd:
X = 0
Y = 1
class EndPointCmd:
X = 0
Y = 1
class OriginalColorsCmd:
COLOR_1 = 0
COLOR_2 = 1
COLOR_3 = 2
COLOR_4 = 3
COLOR_5 = 4
COLOR_6 = 5
COLOR_7 = 6
COLOR_8 = 7
COLOR_9 = 8
COLOR_10 = 9
COLOR_11 = 10
COLOR_12 = 11
class ColorPoints:
VALUE = 0
class Effect:
TYPE = 0
INDEX = 1
NP = 2
ENABLED = 3
class LayerEffect: # Add new Index class
INDEX = 0
VALUE = 1
class Dropdown:
INDEX = 0
VALUE = 1
class NO_VALUE:
INDEX = 0
VALUE = 1
class Ignored:
INDEX = 0
VALUE = 1
class Slider:
INDEX = 0
VALUE = 1
class Color:
NAME_INDEX = 0 # Using NAME_INDEX to avoid confusion with INDEX
INDEX = 1
R = 2
G = 3
B = 4
class Merge:
# merge命令的name会存储在string_params中
pass
class MergeMode:
MODE = 0
class Mask:
INDEX = 0
INV = 1
MODE = 2 # mode will be stored as string
# nm will be stored in string_params
class MaskPt:
A = 0
IX = 1
class MaskPtK:
C = 0 # closed
class MaskPtKValues: # For i, o, v
V1 = 0
V2 = 1
V3 = 2
V4 = 3
V5 = 4
V6 = 5
V7 = 6
V8 = 7
V9 = 8
V10 = 9
V11 = 10
V12 = 11
V13 = 12
V14 = 13
V15 = 14
V16 = 15
V17 = 16
V18 = 17
V19 = 18
V20 = 19
COUNT = 20
class MaskO: # For mask_o
A = 0
K = 1
IX = 2
class MaskX: # For mask_x
A = 0
K = 1
IX = 2
class MaskPtKeyframe:
INDEX = 0
T = 1
class MaskPtKfI:
X = 0
Y = 1
class MaskPtKfO:
X = 0
Y = 1
class MaskPtKfShape:
INDEX = 0
C = 1 # closed
class MaskPtKfShapeValues: # For shape_i, shape_o, shape_v
V1 = 0
V2 = 1
V3 = 2
V4 = 3
V5 = 4
V6 = 5
V7 = 6
V8 = 7
V9 = 8
V10 = 9
V11 = 10
V12 = 11
V13 = 12
V14 = 13
V15 = 14
V16 = 15
V17 = 16
V18 = 17
V19 = 18
V20 = 19
COUNT = 20 # Add this to store the count
class TrPosition:
X = 0
Y = 1
class TrAnchor:
X = 0
Y = 1
class TrRotation:
VALUE = 0
class TrStartOpacity:
VALUE = 0
class TrEndOpacity:
VALUE = 0
class ZigZag:
NAME_INDEX = 0 # Will store in string_params
IX = 1
class Frequency:
VALUE = 0
class Amplitude:
VALUE = 0
class PointType:
VALUE = 0
class Animator:
# nm will be stored in string_params
pass
class RangeSelector:
T = 0
R = 1
B = 2
SH = 3
RN = 4
class RangeStart:
A = 0
class RangeStartKeyframe:
T = 0
S = 1
I_X = 2
I_Y = 3
O_X = 4
O_Y = 5
class Amount:
A = 0
K = 1
IX = 2
class MaxEase:
A = 0
K = 1
IX = 2
class MinEase:
A = 0
K = 1
IX = 2
class Radius:
VALUE = 0
class RangeEnd:
A = 0
class RangeEndKeyframe:
T = 0
S = 1
I_X = 2
I_Y = 3
O_X = 4
O_Y = 5
#class RangeOffset:
# A = 0
class RangeOffsetKeyframe:
T = 0
S = 1
I_X = 2
I_Y = 3
O_X = 4
O_Y = 5
class SM:
A = 0
K = 1
IX = 2
class OpacityAnimators:
A = 0
K = 1
IX = 2
class ScaleAnimators:
A = 0
K_X = 1
K_Y = 2
K_Z = 3
IX = 4
class RotationAnimators:
A = 0
K = 1
IX = 2
class PositionAnimators:
A = 0
K_X = 1
K_Y = 2
K_Z = 3
IX = 4
class TrackingAnimators:
A = 0
K = 1
IX = 2
class Dashes:
# Container command, no parameters
pass
class Dash:
TYPE = 0 # Store type as numeric (0 for "d", 1 for "g", 2 for "o")
LENGTH = 1 # dash length
V_IX = 2 # v_ix parameter
class DashAnimated:
TYPE = 0 # Store type as numeric
V_IX = 1 # v_ix parameter
class DashKeyframe:
T = 0
S = 1
I_X = 2
I_Y = 3
O_X = 4
O_Y = 5
class DashOffset:
O = 0 # offset value
# Parameter dimension (fixed length for all commands)
PARAM_DIM = 50
PAD_VAL = -2001
def __init__(self, commands, params, seq_len=None, PAD_VAL=-2001, flattened_data=None):
"""Initialize LottieTensor"""
self.PAD_VAL = PAD_VAL
self.commands = commands.reshape(-1, 1).long()
self.params = params.float()
self.seq_len = torch.tensor(len(commands)) if seq_len is None else seq_len
self.sos_token = torch.tensor([LottieTensor.CMD_SOS]).unsqueeze(-1).long()
self.eos_token = self.pad_token = torch.tensor([LottieTensor.CMD_EOS]).unsqueeze(-1).long()
# Store original string values
self.string_params = {}
@staticmethod
def _parse_easing_value(value_str: str) -> int:
"""Helper function to parse easing values and return as int"""
if not value_str:
return 0
# Handle quoted format "0.833 0.833 0.833"
if value_str.startswith('"') and value_str.endswith('"'):
value_str = value_str[1:-1]
# Handle space-separated (take first value)
parts = value_str.split()
if parts:
try:
return round(float(parts[0]))
except ValueError:
return 0
# Try to parse as plain number
try:
return round(float(value_str))
except ValueError:
return 0
@staticmethod
def _parse_multi_easing_values(value_str: str) -> List[int]:
"""Parse multi-dimensional easing values like '0.3 0.3 0.3' and return as int list"""
values = [0, 0, 0]
if not value_str:
return values
# Handle quoted format "0.3 0.3 0.3"
if value_str.startswith('"') and value_str.endswith('"'):
value_str = value_str[1:-1]
# Parse space-separated values
parts = value_str.split()
for i, part in enumerate(parts[:3]):
try:
values[i] = round(float(part))
except ValueError:
values[i] = 0
# If only one value provided, use it for all dimensions
if len(parts) == 1 and parts[0]:
try:
val = round(float(parts[0]))
values = [val, val, val]
except ValueError:
pass
return values
@staticmethod
def from_sequence(sequence: str) -> 'LottieTensor':
"""Convert a string sequence to LottieTensor"""
raw_lines = [line.strip() for line in sequence.strip().split('\n') if line.strip()]
# Process each line and extract all commands
lines = []
for raw_line in raw_lines:
# Find all commands in the line (commands are enclosed in parentheses)
import re
commands_in_line = re.findall(r'\([^)]+\)', raw_line)
lines.extend(commands_in_line)
commands = []
params_list = []
string_params = {}
current_context = None
for idx, line in enumerate(lines):
if not (line.startswith('(') and line.endswith(')')):
continue
# Extract command name and attributes
content = line[1:-1].strip()
# Handle end tags
if content.startswith('/'):
cmd = content
if cmd in LottieTensor.COMMAND_TO_IDX:
commands.append(LottieTensor.COMMAND_TO_IDX[cmd])
params_list.append([LottieTensor.PAD_VAL] * LottieTensor.PARAM_DIM)
# Reset context for certain end tags
if cmd in ["/position", "/scale", "/opacity", "/rotation", "/keyframe", "/anchor", "/path", "/width_animated",
"/position_x", "/position_y", "/position_z", "/tm", "/range_start", "/range_end", "/animator_properties",
"/start", "/end", "/offset", "/color_animated", "/rounded"]:
current_context = None
continue
# Parse command and attributes
parts = content.split(' ', 1)
cmd = parts[0]
# Handle quoted commands
if cmd.startswith('"') and cmd.endswith('"'):
cmd = cmd
attrs_str = parts[1] if len(parts) > 1 else ""
# Update context - modified to track path animation, width animation, and individual position components
if cmd in ["position", "scale", "opacity", "rotation", "anchor"]:
current_context = cmd
elif cmd in ["position_x", "position_y", "position_z"]:
# Check if animated
if "animated" in attrs_str and "true" in attrs_str.lower():
current_context = cmd
elif cmd == "path" and "animated" in attrs_str:
current_context = "path"
elif cmd == "width_keyframe":
current_context = "width"
elif cmd == "start" and "animated" in attrs_str and "true" in attrs_str.lower():
current_context = "trim_start"
elif cmd == "end" and "animated" in attrs_str and "true" in attrs_str.lower():
current_context = "trim_end"
elif cmd == "offset" and "animated" in attrs_str and "true" in attrs_str.lower():
current_context = "trim_offset"
elif cmd == "mask_x":
# Check if animated (a=1)
mask_x_attrs = LottieTensor._parse_attributes(attrs_str)
if float(mask_x_attrs.get("a", 0)) > 0.5:
current_context = "mask_x"
elif cmd == "scale_animators":
# Check if animated
scale_animators_attrs = LottieTensor._parse_attributes(attrs_str)
if float(scale_animators_attrs.get("a", 0)) > 0.5:
current_context = "scale_animators"
elif cmd == "rotation_animators":
# Check if animated
rotation_animators_attrs = LottieTensor._parse_attributes(attrs_str)
if float(rotation_animators_attrs.get("a", 0)) > 0.5:
current_context = "rotation_animators"
elif cmd == "opacity_animators":
# Check if animated
opacity_animators_attrs = LottieTensor._parse_attributes(attrs_str)
if float(opacity_animators_attrs.get("a", 0)) > 0.5:
current_context = "opacity_animators"
elif cmd == "position_animators":
# Check if animated
position_animators_attrs = LottieTensor._parse_attributes(attrs_str)
if float(position_animators_attrs.get("a", 0)) > 0.5:
current_context = "position_animators"
elif cmd == "tracking_animators":
# Check if animated
tracking_animators_attrs = LottieTensor._parse_attributes(attrs_str)
if float(tracking_animators_attrs.get("a", 0)) > 0.5:
current_context = "tracking_animators"
elif cmd == "rect_rounded" and "animated" in attrs_str and "true" in attrs_str.lower():
current_context = "rect_rounded"
if cmd not in LottieTensor.COMMAND_TO_IDX:
continue
cmd_idx = LottieTensor.COMMAND_TO_IDX[cmd]
cmd_key = f"{len(commands)}" # Use command index as key
commands.append(cmd_idx)
# Initialize parameters
params = [LottieTensor.PAD_VAL] * LottieTensor.PARAM_DIM
attrs = LottieTensor._parse_attributes(attrs_str)
# Parse parameters based on command type
if cmd_idx == LottieTensor.CMD_ANIMATION:
# Store original string values
#string_params[f"{cmd_key}_v"] = attrs.get("v", "5.12.1")
#string_params[f"{cmd_key}_nm"] = attrs.get("nm", "Comp 1")
#string_params[f"{cmd_key}_markers"] = attrs.get("markers", "[]")
#string_params[f"{cmd_key}_props"] = attrs.get("props", "{}")
params[LottieTensor.Index.Animation.FR] = round(float(attrs.get("fr", 60)))
params[LottieTensor.Index.Animation.IP] = round(float(attrs.get("ip", 0)))
params[LottieTensor.Index.Animation.OP] = round(float(attrs.get("op", 150)))
params[LottieTensor.Index.Animation.W] = round(float(attrs.get("w", 512)))
params[LottieTensor.Index.Animation.H] = round(float(attrs.get("h", 512)))
params[LottieTensor.Index.Animation.DDD] = int(attrs.get("ddd", 0))
#eos token
#print("params", params)
elif cmd_idx == LottieTensor.CMD_LAYER:
# Store layer name and string attributes
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Layer")
# 必需的属性
#params[LottieTensor.Index.Layer.INDEX] = float(attrs.get("index", 0))
params[LottieTensor.Index.Layer.INDEX] = LottieTensor._index_clamp_value(round(float(attrs.get("index", "0")))) # index 0-100
#params[LottieTensor.Index.Layer.IN_POINT] = float(attrs.get("in_point", 0))
#params[LottieTensor.Index.Layer.OUT_POINT] = float(attrs.get("out_point", 60))
params[LottieTensor.Index.Layer.IN_POINT] = LottieTensor._clamp_value(round(float(attrs.get("in_point", 0)))) #ip -2000-2000
params[LottieTensor.Index.Layer.OUT_POINT] = LottieTensor._clamp_value(round(float(attrs.get("out_point", 60)))) #op -2000-2000
params[LottieTensor.Index.Layer.START_TIME] = LottieTensor._clamp_value(round(float(attrs.get("start_time", 0)))) #st -2000-2000
# 可选属性 - 只在存在时解析,不设置默认值
if "ddd" in attrs:
params[LottieTensor.Index.Layer.DDD] = float(attrs.get("ddd")) #0-1
if "hd" in attrs:
params[LottieTensor.Index.Layer.HD] = 1.0 if attrs.get("hd").lower() == "true" else 0.0 # 0-1
if "cp" in attrs:
params[LottieTensor.Index.Layer.CP] = 1.0 if attrs.get("cp").lower() == "true" else 0.0 # 0-1
if "hasMask" in attrs:
params[LottieTensor.Index.Layer.HAS_MASK] = 1.0 if attrs.get("hasMask").lower() == "true" else 0.0 # 0-1
if "ao" in attrs:
params[LottieTensor.Index.Layer.AO] = int(attrs.get("ao")) # 0-1
if "tt" in attrs:
params[LottieTensor.Index.Layer.TT] = int(attrs.get("tt")) # 0-4
if "tp" in attrs:
params[LottieTensor.Index.Layer.TP] = int(attrs.get("tp")) #0-100以内
if "td" in attrs:
params[LottieTensor.Index.Layer.TD] = int(attrs.get("td")) #0-1
if "ct" in attrs:
params[LottieTensor.Index.Layer.CT] = int(attrs.get("ct")) #0-1
elif cmd_idx == LottieTensor.CMD_NULL_LAYER:
# Store layer name and string attributes
#string_params[f"{cmd_key}_name"] = attrs.get("name", "null_layer")
#params[LottieTensor.Index.NullLayer.INDEX] = int(attrs.get("index", 0))
params[LottieTensor.Index.NullLayer.INDEX] = LottieTensor._index_clamp_value(round(float(attrs.get("index", 0))))
#params[LottieTensor.Index.NullLayer.IN_POINT] = float(attrs.get("in_point", 0))
#params[LottieTensor.Index.NullLayer.OUT_POINT] = float(attrs.get("out_point", 60))
params[LottieTensor.Index.NullLayer.IN_POINT] = LottieTensor._clamp_value(round(float(attrs.get("in_point", 0))))
params[LottieTensor.Index.NullLayer.OUT_POINT] = LottieTensor._clamp_value(round(float(attrs.get("out_point", 60))))
params[LottieTensor.Index.NullLayer.START_TIME] = LottieTensor._clamp_value(round(float(attrs.get("start_time", 0))))
if "hd" in attrs:
params[LottieTensor.Index.PrecompLayer.HD] = 1.0 if attrs.get("hd").lower() == "true" else 0.0
if "cp" in attrs:
params[LottieTensor.Index.PrecompLayer.CP] = 1.0 if attrs.get("cp").lower() == "true" else 0.0
if "hasMask" in attrs:
params[LottieTensor.Index.PrecompLayer.HAS_MASK] = 1.0 if attrs.get("hasMask").lower() == "true" else 0.0
if "ao" in attrs:
params[LottieTensor.Index.PrecompLayer.AO] = int(attrs.get("ao"))
if "tt" in attrs:
params[LottieTensor.Index.PrecompLayer.TT] = int(attrs.get("tt"))
if "tp" in attrs:
params[LottieTensor.Index.PrecompLayer.TP] = int(attrs.get("tp"))
if "td" in attrs:
params[LottieTensor.Index.PrecompLayer.TD] = int(attrs.get("td"))
elif cmd_idx == LottieTensor.CMD_PRECOMP_LAYER:
# Parse name more carefully to handle names with spaces
name = attrs.get("name", "precomp_layer")
# If name wasn't properly captured (e.g., due to spaces), try regex
if name == "precomp_layer" or not name:
# Look for name="..." pattern in the original attrs_str
import re
name_match = re.search(r'name="([^"]*)"', attrs_str)
if name_match:
name = name_match.group(1)
else:
# Try without quotes
name_match = re.search(r'name=([^\s]+)', attrs_str)
if name_match:
name = name_match.group(1)
else:
name = "precomp_layer"
#string_params[f"{cmd_key}_name"] = name
# Parse numeric parameters
#params[LottieTensor.Index.PrecompLayer.INDEX] = float(attrs.get("index", 0))
params[LottieTensor.Index.PrecompLayer.INDEX] = LottieTensor._index_clamp_value(round(float(attrs.get("index", 0))))
#params[LottieTensor.Index.PrecompLayer.IN_POINT] = float(attrs.get("in_point", 0))
#params[LottieTensor.Index.PrecompLayer.OUT_POINT] = float(attrs.get("out_point", 120))
params[LottieTensor.Index.PrecompLayer.IN_POINT] = LottieTensor._clamp_value(round(float(attrs.get("in_point", 0))))
params[LottieTensor.Index.PrecompLayer.OUT_POINT] = LottieTensor._clamp_value(round(float(attrs.get("out_point", 120))))
params[LottieTensor.Index.PrecompLayer.START_TIME] = LottieTensor._clamp_value(round(float(attrs.get("start_time", 0))))
# 可选属性 - 只在存在时解析,不设置默认值
if "h" in attrs:
params[LottieTensor.Index.PrecompLayer.H] = round(float(attrs.get("h"))) #0-2000
if "w" in attrs:
params[LottieTensor.Index.PrecompLayer.W] = round(float(attrs.get("w"))) #0-2000
if "ddd" in attrs:
params[LottieTensor.Index.PrecompLayer.DDD] = int(attrs.get("ddd")) #0-1
if "hd" in attrs:
params[LottieTensor.Index.PrecompLayer.HD] = 1.0 if attrs.get("hd").lower() == "true" else 0.0 #0-1
if "cp" in attrs:
params[LottieTensor.Index.PrecompLayer.CP] = 1.0 if attrs.get("cp").lower() == "true" else 0.0 #0-1
if "hasMask" in attrs:
params[LottieTensor.Index.PrecompLayer.HAS_MASK] = 1.0 if attrs.get("hasMask").lower() == "true" else 0.0 #0-1
if "ao" in attrs:
params[LottieTensor.Index.PrecompLayer.AO] = int(attrs.get("ao")) #0-1
if "tt" in attrs:
params[LottieTensor.Index.PrecompLayer.TT] = int(attrs.get("tt"))
if "tp" in attrs:
params[LottieTensor.Index.PrecompLayer.TP] = int(attrs.get("tp"))
if "td" in attrs:
params[LottieTensor.Index.PrecompLayer.TD] = int(attrs.get("td"))
if "ct" in attrs:
params[LottieTensor.Index.PrecompLayer.CT] = int(attrs.get("ct"))
elif cmd_idx == LottieTensor.CMD_TEXT_LAYER:
# 存储name
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Text Layer")
#params[LottieTensor.Index.TextLayer.INDEX] = float(attrs.get("index", 0))
params[LottieTensor.Index.TextLayer.INDEX] = LottieTensor._index_clamp_value(round(float(attrs.get("index", 0))))
#params[LottieTensor.Index.TextLayer.IN_POINT] = float(attrs.get("in_point", 0))
#params[LottieTensor.Index.TextLayer.OUT_POINT] = float(attrs.get("out_point", 60))
params[LottieTensor.Index.TextLayer.IN_POINT] = LottieTensor._clamp_value(round(float(attrs.get("in_point", 0))))
params[LottieTensor.Index.TextLayer.OUT_POINT] = LottieTensor._clamp_value(round(float(attrs.get("out_point", 60))))
params[LottieTensor.Index.TextLayer.START_TIME] = LottieTensor._clamp_value(round(float(attrs.get("start_time", 0))))
params[LottieTensor.Index.TextLayer.HAS_MASK] = 1.0 if attrs.get("hasMask", "false").lower() == "true" else 0.0 # 新增
elif cmd_idx == LottieTensor.CMD_SOLID_LAYER:
# Store string attributes
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Solid Layer")
#string_params[f"{cmd_key}_color"] = attrs.get("color", "#000000")
hex_color = attrs.get("color", "#00000000")
# Remove '#' if present
hex_color = hex_color.lstrip('#')
# Convert to RGB
r = int(hex_color[0:2], 16) if len(hex_color) >= 2 else 0
g = int(hex_color[2:4], 16) if len(hex_color) >= 4 else 0
b = int(hex_color[4:6], 16) if len(hex_color) >= 6 else 0
a = int(hex_color[6:8], 16) if len(hex_color) >= 8 else 0
# Store RGB values as separate parameters
params[LottieTensor.Index.SolidLayer.COLOR_R] = round(float(r))
params[LottieTensor.Index.SolidLayer.COLOR_G] = round(float(g))
params[LottieTensor.Index.SolidLayer.COLOR_B] = round(float(b))
params[LottieTensor.Index.SolidLayer.COLOR_A] = round(float(a)) #这里的color都是0-255吧
#params[LottieTensor.Index.SolidLayer.INDEX] = float(attrs.get("index", 0))
params[LottieTensor.Index.SolidLayer.INDEX] = LottieTensor._index_clamp_value(round(float(attrs.get("index", 0))))
#params[LottieTensor.Index.SolidLayer.IN_POINT] = float(attrs.get("in_point", 0))
#params[LottieTensor.Index.SolidLayer.OUT_POINT] = float(attrs.get("out_point", 60))
params[LottieTensor.Index.SolidLayer.IN_POINT] = LottieTensor._clamp_value(round(float(attrs.get("in_point", 0))))
params[LottieTensor.Index.SolidLayer.OUT_POINT] = LottieTensor._clamp_value(round(float(attrs.get("out_point", 60))))
params[LottieTensor.Index.SolidLayer.START_TIME] = LottieTensor._clamp_value(round(float(attrs.get("start_time", 0))))
params[LottieTensor.Index.SolidLayer.WIDTH] = LottieTensor._clamp_value(round(float(attrs.get("width", 512))))
params[LottieTensor.Index.SolidLayer.HEIGHT] = LottieTensor._clamp_value(round(float(attrs.get("height", 512))))
params[LottieTensor.Index.SolidLayer.HAS_MASK] = 1.0 if attrs.get("hasMask", "false").lower() == "true" else 0.0
elif cmd_idx in [LottieTensor.CMD_FONTS, LottieTensor.CMD_FONTS_END, LottieTensor.CMD_CHARS, LottieTensor.CMD_CHARS_END, LottieTensor.CMD_CHAR_SHAPES, LottieTensor.CMD_CHAR_SHAPES_END, LottieTensor.CMD_TEXT_KEYFRAMES, LottieTensor.CMD_MORE_OPTIONS, LottieTensor.CMD_OPACITY_ANIMATED_END, LottieTensor.CMD_END_END, LottieTensor.CMD_START_END, LottieTensor.CMD_OFFSET_END, LottieTensor.CMD_OPACITY_ANIMATORS_END]:
pass
elif cmd_idx == LottieTensor.CMD_TEXT_KEYFRAME:
if LottieTensor.tokenizer is None:
LottieTensor.init_tokenizer()
# Parse all attributes from text_keyframe
params[LottieTensor.Index.TextKeyframe.T] = LottieTensor._clamp_value(round(float(attrs.get("t", 0))))
# Parse stroke_width as a numeric parameter
stroke_width_str = attrs.get("stroke_width", "0")
try:
params[LottieTensor.Index.TextKeyframe.STROKE_WIDTH] = round(float(stroke_width_str))
except ValueError:
params[LottieTensor.Index.TextKeyframe.STROKE_WIDTH] = 0
# Parse offset as a boolean (1.0 for true, 0.0 for false)
offset_str = attrs.get("offset", "false")
params[LottieTensor.Index.TextKeyframe.OFFSET] = 1 if offset_str.lower() == "true" else 0.0
# Parse wrap_position array (新增)
wrap_position_str = attrs.get("wrap_position", "")
if wrap_position_str:
if wrap_position_str.startswith("[") and wrap_position_str.endswith("]"):
wrap_position_str = wrap_position_str[1:-1]
pos_parts = wrap_position_str.split(",")
if len(pos_parts) >= 2:
try:
params[LottieTensor.Index.TextKeyframe.WRAP_POSITION_X] = round(float(pos_parts[0].strip()))
params[LottieTensor.Index.TextKeyframe.WRAP_POSITION_Y] = round(float(pos_parts[1].strip()))
except ValueError:
pass
# Parse wrap_size array (新增)
wrap_size_str = attrs.get("wrap_size", "")
if wrap_size_str:
if wrap_size_str.startswith("[") and wrap_size_str.endswith("]"):
wrap_size_str = wrap_size_str[1:-1]
size_parts = wrap_size_str.split(",")
if len(size_parts) >= 2:
try:
params[LottieTensor.Index.TextKeyframe.WRAP_SIZE_X] = round(float(size_parts[0].strip()))
params[LottieTensor.Index.TextKeyframe.WRAP_SIZE_Y] = round(float(size_parts[1].strip()))
except ValueError:
pass
# Store all text_keyframe attributes in string_params
#string_params[f"{cmd_key}_font_size"] = attrs.get("font_size", "12")
#string_params[f"{cmd_key}_font_family"] = attrs.get("font_family", "")
#string_params[f"{cmd_key}_text"] = attrs.get("text", "")
#string_params[f"{cmd_key}_ca"] = attrs.get("ca", "1")
#string_params[f"{cmd_key}_justify"] = attrs.get("justify", "0")
#string_params[f"{cmd_key}_tracking"] = attrs.get("tracking", "0")
#string_params[f"{cmd_key}_line_height"] = attrs.get("line_height", "0")
#string_params[f"{cmd_key}_letter_spacing"] = attrs.get("letter_spacing", "0")
params[LottieTensor.Index.TextKeyframe.FONT_SIZE] = round(float(attrs.get("font_size", 12)))
params[LottieTensor.Index.TextKeyframe.CA] = int(attrs.get("ca", 1))
params[LottieTensor.Index.TextKeyframe.JUSTIFY] = int(attrs.get("justify", 0))
params[LottieTensor.Index.TextKeyframe.TRACKING] = int(float(attrs.get("tracking", 0)))
params[LottieTensor.Index.TextKeyframe.LINE_HEIGHT] = round(float(attrs.get("line_height", 0)))
params[LottieTensor.Index.TextKeyframe.LETTER_SPACING] = int(float(attrs.get("letter_spacing", 0)))
font_family = attrs.get("font_family", "")
if font_family:
font_family_tokens = LottieTensor.tokenizer.encode(font_family, add_special_tokens=False)
# Store up to 10 tokens
for i, token in enumerate(font_family_tokens[:10]):
params[LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKENS_START + i] = int(token)
params[LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKEN_COUNT] = int(len(font_family_tokens[:10]))
else:
params[LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKEN_COUNT] = 0
# Tokenize text
text = attrs.get("text", "")
if text:
text_tokens = LottieTensor.tokenizer.encode(text, add_special_tokens=False)
# Store up to 15 tokens
for i, token in enumerate(text_tokens[:15]):
params[LottieTensor.Index.TextKeyframe.TEXT_TOKENS_START + i] = int(token)
params[LottieTensor.Index.TextKeyframe.TEXT_TOKEN_COUNT] = int(len(text_tokens[:15]))
else:
params[LottieTensor.Index.TextKeyframe.TEXT_TOKEN_COUNT] = 0
# Parse fill_color array
fill_color_str = attrs.get("fill_color", "[0,0,0]")
if fill_color_str.startswith("[") and fill_color_str.endswith("]"):
fill_color_str = fill_color_str[1:-1]
color_parts = fill_color_str.split(",")
#string_params[f"{cmd_key}_fill_color"] = ",".join([p.strip() for p in color_parts])
if len(color_parts) >= 3:
params[LottieTensor.Index.TextKeyframe.FILL_COLOR_R] = round(float(color_parts[0].strip()) * 255)
params[LottieTensor.Index.TextKeyframe.FILL_COLOR_G] = round(float(color_parts[1].strip()) * 255)
params[LottieTensor.Index.TextKeyframe.FILL_COLOR_B] = round(float(color_parts[2].strip()) * 255)
# Parse stroke_color array (if present)
stroke_color_str = attrs.get("stroke_color", "")
if stroke_color_str:
if stroke_color_str.startswith("[") and stroke_color_str.endswith("]"):
stroke_color_str = stroke_color_str[1:-1]
color_parts = stroke_color_str.split(",")
if len(color_parts) >= 3:
params[LottieTensor.Index.TextKeyframe.STROKE_COLOR_R] = round(float(color_parts[0].strip()) * 255)
params[LottieTensor.Index.TextKeyframe.STROKE_COLOR_G] = round(float(color_parts[1].strip()) * 255)
params[LottieTensor.Index.TextKeyframe.STROKE_COLOR_B] = round(float(color_parts[2].strip()) * 255)
else:
params[LottieTensor.Index.TextKeyframe.HAS_STROKE_COLOR] = 0
elif cmd_idx == LottieTensor.CMD_MORE_OPTIONS:
# Parse the entire more_options line
parts_list = attrs_str.split()
i = 0
while i < len(parts_list):
if parts_list[i] == "g" and i + 1 < len(parts_list):
params[LottieTensor.Index.MoreOptions.G] = round(float(parts_list[i + 1])) #1-4
i += 2
elif parts_list[i] == "alignment" and i + 1 < len(parts_list):
if parts_list[i + 1].startswith("a="):
params[LottieTensor.Index.MoreOptions.ALIGNMENT_A] = round(float(parts_list[i + 1].split("=")[1]))
i += 2
else:
i += 1
elif parts_list[i] == "alignment_k" and i + 2 < len(parts_list):
params[LottieTensor.Index.MoreOptions.ALIGNMENT_K1] = round(float(parts_list[i + 1]))
params[LottieTensor.Index.MoreOptions.ALIGNMENT_K2] = round(float(parts_list[i + 2]))
i += 3
elif parts_list[i] == "alignment_ix" and i + 1 < len(parts_list):
params[LottieTensor.Index.MoreOptions.ALIGNMENT_IX] = round(float(parts_list[i + 1]))
i += 2
else:
i += 1
elif cmd_idx == LottieTensor.CMD_REFERENCE_ID:
if LottieTensor.tokenizer is None:
LottieTensor.init_tokenizer()
# Extract reference_id from attrs_str
ref_id_match = re.search(r'"([^"]*)"', attrs_str)
if ref_id_match:
reference_id = ref_id_match.group(1)
else:
# Try without quotes
parts = attrs_str.strip().split()
if parts:
reference_id = parts[0]
else:
reference_id = "comp_0"
# Tokenize reference_id
id_tokens = LottieTensor.tokenizer.encode(reference_id, add_special_tokens=False)[:10] # Limit to 10 tokens
for i, token_id in enumerate(id_tokens):
if i < 10:
params[LottieTensor.Index.ReferenceId.ID_TOKEN_0 + i] = int(token_id)
params[LottieTensor.Index.ReferenceId.ID_TOKEN_COUNT] = int(len(id_tokens))
elif cmd_idx == LottieTensor.CMD_DIMENSIONS:
# 处理dimensions命令
params[LottieTensor.Index.Dimensions.WIDTH] = round(float(attrs.get("width", 512))) #0-2000
params[LottieTensor.Index.Dimensions.HEIGHT] = round(float(attrs.get("height", 512))) #0-2000
# 3. Modify the stroke parsing in from_sequence method:
elif cmd_idx == LottieTensor.CMD_STROKE:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Stroke")
# Check if color is animated
color_animated = attrs.get("color_animated", "false").lower() == "true"
params[LottieTensor.Index.Stroke.COLOR_ANIMATED] = 1.0 if color_animated else 0.0 # 0-1
if not color_animated:
# Parse static color and convert from 0-1 to 0-255 range
params[LottieTensor.Index.Stroke.R] = round(float(attrs.get("r", 0)) * 255) # 0-1 → 0-255
params[LottieTensor.Index.Stroke.G] = round(float(attrs.get("g", 0)) * 255) # 0-1 → 0-255
params[LottieTensor.Index.Stroke.B] = round(float(attrs.get("b", 0)) * 255) # 0-1 → 0-255
params[LottieTensor.Index.Stroke.A] = round(float(attrs.get("a", 1)) * 255) # 0-1 → 0-255
params[LottieTensor.Index.Stroke.COLOR_DIM] = int(attrs.get("color_dim", 4)) #3-4
params[LottieTensor.Index.Stroke.HAS_C_A] = 1.0 if attrs.get("has_c_a", "").lower() == "true" else 0.0 #0-1
params[LottieTensor.Index.Stroke.HAS_C_IX] = 1.0 if attrs.get("has_c_ix", "").lower() == "true" else 0.0 #0-1
params[LottieTensor.Index.Stroke.C_IX] = int(attrs.get("c_ix", 3)) #2-4
params[LottieTensor.Index.Stroke.BM] = int(attrs.get("bm", 0)) #0-1
params[LottieTensor.Index.Stroke.LC] = int(attrs.get("lc", 1)) #1-3
params[LottieTensor.Index.Stroke.LJ] = int(attrs.get("lj", 1)) #1-3
params[LottieTensor.Index.Stroke.ML] = int(float((attrs.get("ml", "4")))) #0-50
# Handle width or width_animated
if "width_animated" in attrs and attrs.get("width_animated", "").lower() == "true":
params[LottieTensor.Index.Stroke.WIDTH_ANIMATED] = 1.0
current_context = "width"
# ADD THIS: Append width_animated command after stroke
params_list.append(params)
commands.append(LottieTensor.CMD_WIDTH_ANIMATED) # Note: need to define this constant
params_list.append([LottieTensor.PAD_VAL] * LottieTensor.PARAM_DIM)
params = [LottieTensor.PAD_VAL] * LottieTensor.PARAM_DIM # Reset for next command
else:
params[LottieTensor.Index.Stroke.WIDTH_ANIMATED] = 0.0
# Set context for color keyframes if animated
if color_animated:
current_context = "stroke_color"
# 4. Add parsing for color_keyframe command: keframe的s和i_x, i_y, o_x, o_y是不能越界的,越界需要去除
elif cmd_idx == LottieTensor.CMD_COLOR_KEYFRAME:
# Parse color keyframe parameters
params[LottieTensor.Index.Keyframe.T] = round(float(attrs.get("t", 0))) #-2000-2000
params[LottieTensor.Index.Keyframe.S1] = round(float(attrs.get("r", 0)) * 255) # Use S1 for R
params[LottieTensor.Index.Keyframe.S2] = round(float(attrs.get("g", 0)) * 255) # Use S2 for G
params[LottieTensor.Index.Keyframe.S3] = round(float(attrs.get("b", 0)) * 255) # Use S3 for B
params[LottieTensor.Index.Keyframe.E1] = round(float(attrs.get("a", 1)) * 255) # Use E1 for A
# Parse easing parameters
params[LottieTensor.Index.Keyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0"))*100))
params[LottieTensor.Index.Keyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0"))*100))
params[LottieTensor.Index.Keyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0"))*100))
params[LottieTensor.Index.Keyframe.O_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0"))*100))
elif cmd_idx == LottieTensor.CMD_OPACITY_ANIMATED:
# Check if animated
if attrs.get("true", "false").lower() == "true" or "true" in attrs_str.lower():
current_context = "opacity_animated"
elif cmd_idx == LottieTensor.CMD_OPACITY_KEYFRAME:
# Parse opacity keyframe parameters
params[LottieTensor.Index.Keyframe.T] = round(float(attrs.get("t", 0)))
# Parse s parameter if present
if "s" in attrs:
s_str = attrs.get("s", "0").strip('"')
params[LottieTensor.Index.Keyframe.S1] = round(float(s_str))
# Parse easing parameters if present
if "i_x" in attrs or "i_y" in attrs or "o_x" in attrs or "o_y" in attrs:
params[LottieTensor.Index.Keyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0"))*100))
params[LottieTensor.Index.Keyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0"))*100))
params[LottieTensor.Index.Keyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0"))*100))
params[LottieTensor.Index.Keyframe.O_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0"))*100))
elif cmd_idx == LottieTensor.CMD_WIDTH_KEYFRAME:
params[LottieTensor.Index.WidthKeyframe.T] = round(float(attrs.get("t", 0)))
# 处理s参数 - 改成乘以10
if "s" in attrs:
s_str = attrs.get("s", "0").strip('"')
try:
s_val = round(float(s_str) * 10)
s_val = max(0, min(10000, s_val)) # 裁剪
params[LottieTensor.Index.WidthKeyframe.S] = s_val
except ValueError:
params[LottieTensor.Index.WidthKeyframe.S] = 0.0
# easing参数保持不变
params[LottieTensor.Index.WidthKeyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0"))*100))
params[LottieTensor.Index.WidthKeyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0"))*100))
params[LottieTensor.Index.WidthKeyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0"))*100))
params[LottieTensor.Index.WidthKeyframe.O_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0"))*100))
elif cmd_idx == LottieTensor.CMD_POSITION:
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
elif attrs.get("separated", "").lower() == "true":
# Handle separated position (for 3D layers)
params[LottieTensor.Index.Transform.ANIMATED] = 2.0 # Use 2.0 to indicate separated
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# Parse position values
pos_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
pos_parts.append(part)
except ValueError:
pass
if len(pos_parts) >= 1:
params[LottieTensor.Index.Transform.X] = round(float(pos_parts[0]))
if len(pos_parts) >= 2:
params[LottieTensor.Index.Transform.Y] = round(float(pos_parts[1]))
if len(pos_parts) >= 3:
params[LottieTensor.Index.Transform.Z] = round(float(pos_parts[2]))
elif cmd_idx in [LottieTensor.CMD_POSITION_X, LottieTensor.CMD_POSITION_Y, LottieTensor.CMD_POSITION_Z]:
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1
current_context = cmd # Set context to the specific component
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0
# Parse the value
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Transform.X] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_SCALE:
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# Parse scale values
scale_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
scale_parts.append(part)
except ValueError:
pass
if len(scale_parts) >= 1:
params[LottieTensor.Index.Transform.X] = round(float(scale_parts[0]))
if len(scale_parts) >= 2:
params[LottieTensor.Index.Transform.Y] = round(float(scale_parts[1]))
if len(scale_parts) >= 3:
params[LottieTensor.Index.Transform.Z] = round(float(scale_parts[2]))
elif cmd_idx == LottieTensor.CMD_ROTATION:
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# Parse rotation value
rot_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
rot_parts.append(part)
except ValueError:
pass
if rot_parts:
val = round(float(rot_parts[0]))
# 添加裁剪:将 rotation 限制在 -720 到 720 范围内
val = max(-720, min(720, val % 360 if abs(val) > 720 else val))
params[LottieTensor.Index.Transform.X] = val
elif cmd_idx == LottieTensor.CMD_OPACITY:
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# Parse opacity value
op_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
op_parts.append(part)
except ValueError:
pass
if op_parts:
params[LottieTensor.Index.Transform.X] = round(float(op_parts[0]))
elif cmd_idx == LottieTensor.CMD_ANCHOR:
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# Parse anchor values
anchor_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
anchor_parts.append(part)
except ValueError:
pass
if len(anchor_parts) >= 1:
params[LottieTensor.Index.Transform.X] = round(float(anchor_parts[0]))
if len(anchor_parts) >= 2:
params[LottieTensor.Index.Transform.Y] = round(float(anchor_parts[1]))
if len(anchor_parts) >= 3:
params[LottieTensor.Index.Transform.Z] = round(float(anchor_parts[2]))
elif cmd_idx == LottieTensor.CMD_TM:
params[LottieTensor.Index.Tm.A] = int(attrs.get("a", 1))
#params[LottieTensor.Index.Tm.IX] = float(attrs.get("ix", 2))
# Check if animated
a_value = int(attrs.get("a", 1))
if a_value > 0.5:
current_context = "tm" # Set context for keyframes
else:
current_context = "tm_static" # Different context for static value
# Don't set special context for a=0, let value command be parsed normally
# Add value command parsing
elif cmd_idx == LottieTensor.CMD_VALUE:
# Parse the numeric value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Value.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_SKEW:
# Handle standalone skew command
# Parse the numeric value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
else:
params[LottieTensor.Index.SingleValue.VALUE] = 0
elif cmd_idx == LottieTensor.CMD_SKEW_AXIS:
# Handle standalone skew_axis command
# Parse the numeric value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
else:
params[LottieTensor.Index.SingleValue.VALUE] = 0
elif cmd_idx == LottieTensor.CMD_KEYFRAME:
params[LottieTensor.Index.Keyframe.T] = round(float(attrs.get("t", 0)))
# Check for h parameter (hold keyframe) - for ALL contexts, not just tm
h_value = attrs.get("h", "0")
is_hold = h_value == "1" or h_value.lower() == "true"
# Store h parameter flag using dedicated H_FLAG slot
if is_hold:
params[LottieTensor.Index.Keyframe.H_FLAG] = 1.0 # Using H_FLAG to store h flag
# Parse s parameter based on context - check if s exists
if "s" in attrs:
s_str = attrs.get("s", "0").strip('"')
s_parts = s_str.split()
if current_context in ["rotation", "opacity", "position_x", "position_y", "position_z", "tm", "width",
"trim_start", "trim_end", "trim_offset", "mask_x", "rotation_animators", "opacity_animators", "tracking_animators", "rect_rounded"]: # Added trim contexts
# For single-value properties, only use S1
if s_parts:
params[LottieTensor.Index.Keyframe.S1] = round(float(s_parts[0]))
elif current_context in ["position", "scale", "anchor", "scale_animators", "position_animators", "size"]:
# For position/scale/anchor, use x,y,z values
for i, part in enumerate(s_parts[:3]):
params[LottieTensor.Index.Keyframe.S1 + i] = round(float(part))
elif current_context == "path":
# Path keyframes don't have s parameter
pass
else:
# Default case
for i, part in enumerate(s_parts[:3]):
params[LottieTensor.Index.Keyframe.S1 + i] = round(float(part))
# Parse e parameter based on context - ADD THIS FOR TRIM CONTEXTS
if "e" in attrs:
e_str = attrs.get("e", "0").strip('"')
# Remove brackets if present
if e_str.startswith("[") and e_str.endswith("]"):
e_str = e_str[1:-1]
e_parts = e_str.split(',')
if current_context in ["trim_start", "trim_end", "trim_offset"]:
# For trim contexts, e is a single value
if e_parts:
params[LottieTensor.Index.Keyframe.E1] = round(float(e_parts[0].strip()))
elif current_context == "rotation":
# For rotation, e is a single value
if e_parts:
params[LottieTensor.Index.Keyframe.E1] = round(float(e_parts[0].strip()))
elif current_context == "scale":
# For scale, e has three values
for i, part in enumerate(e_parts[:3]):
params[LottieTensor.Index.Keyframe.E1 + i] = round(float(part.strip()))
# Add other contexts as needed
# Only parse easing parameters if not a hold keyframe
if not is_hold:
# Parse easing parameters based on context
if current_context in ["anchor", "size"]:
# For multi-dimensional properties, parse multiple easing values
i_x_values = LottieTensor._parse_multi_easing_values(attrs.get("i_x", "0"))
i_y_values = LottieTensor._parse_multi_easing_values(attrs.get("i_y", "0"))
o_x_values = LottieTensor._parse_multi_easing_values(attrs.get("o_x", "0"))
o_y_values = LottieTensor._parse_multi_easing_values(attrs.get("o_y", "0"))
# Store all three values - MULTIPLY BY 100 AND ROUND
params[LottieTensor.Index.Keyframe.I_X] = round(i_x_values[0] * 100)
params[LottieTensor.Index.Keyframe.I_X2] = round(i_x_values[1] * 100)
params[LottieTensor.Index.Keyframe.I_X3] = round(i_x_values[2] * 100)
params[LottieTensor.Index.Keyframe.I_Y] = round(i_y_values[0] * 100)
params[LottieTensor.Index.Keyframe.I_Y2] = round(i_y_values[1] * 100)
params[LottieTensor.Index.Keyframe.I_Y3] = round(i_y_values[2] * 100)
params[LottieTensor.Index.Keyframe.O_X] = round(o_x_values[0] * 100)
params[LottieTensor.Index.Keyframe.O_X2] = round(o_x_values[1] * 100)
params[LottieTensor.Index.Keyframe.O_X3] = round(o_x_values[2] * 100)
params[LottieTensor.Index.Keyframe.O_Y] = round(o_y_values[0] * 100)
params[LottieTensor.Index.Keyframe.O_Y2] = round(o_y_values[1] * 100)
params[LottieTensor.Index.Keyframe.O_Y3] = round(o_y_values[2] * 100)
else:
# For single-dimensional properties, parse single easing values - MULTIPLY BY 100 AND ROUND
params[LottieTensor.Index.Keyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0")) * 100))
params[LottieTensor.Index.Keyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0")) * 100))
params[LottieTensor.Index.Keyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0")) * 100))
params[LottieTensor.Index.Keyframe.O_Y] =round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0")) * 100))
# Parse to/ti parameters (always use TO1-TO3 and TI1-TI3 for their actual values)
to_str = attrs.get("to", "")
ti_str = attrs.get("ti", "")
if to_str:
to_values = LottieTensor._extract_array_values(to_str, 3)
for i in range(3):
params[LottieTensor.Index.Keyframe.TO1 + i] = to_values[i]
if ti_str:
ti_values = LottieTensor._extract_array_values(ti_str, 3)
for i in range(3):
params[LottieTensor.Index.Keyframe.TI1 + i] = ti_values[i]
# Parse e parameter based on context
if "e" in attrs:
e_str = attrs.get("e", "0").strip('"')
# Remove brackets if present
if e_str.startswith("[") and e_str.endswith("]"):
e_str = e_str[1:-1]
e_parts = e_str.split(',')
if current_context == "rotation":
# For rotation, e is a single value
if e_parts:
params[LottieTensor.Index.Keyframe.E1] = round(float(e_parts[0].strip()))
elif current_context == "scale":
# For scale, e has three values
for i, part in enumerate(e_parts[:3]):
params[LottieTensor.Index.Keyframe.E1 + i] = round(float(part.strip()))
# Add other contexts as needed
elif cmd_idx == LottieTensor.CMD_GROUP:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Group")
#string_params[f"{cmd_key}_mn"] = attrs.get("mn", "ADBE Vector Group")
params[LottieTensor.Index.Group.IX] = int(attrs.get("ix", 1)) #0-1000
params[LottieTensor.Index.Group.CIX] = int(attrs.get("cix", 2)) #1-10
params[LottieTensor.Index.Group.BM] = int(attrs.get("bm", 0)) #0-1
params[LottieTensor.Index.Group.HD] = 1.0 if attrs.get("hd", "false").lower() == "true" else 0.0 #0-1
params[LottieTensor.Index.Group.NP] = int(attrs.get("np", 0)) # 0-1000
elif cmd_idx == LottieTensor.CMD_PATH:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Path")
#string_params[f"{cmd_key}_mn"] = attrs.get("mn", "ADBE Vector Path") # Add mn
params[LottieTensor.Index.Path.IX] = int(attrs.get("ix", 1)) # 0-1000
params[LottieTensor.Index.Path.IND] = int(attrs.get("ind", 0))
params[LottieTensor.Index.Path.KS_IX] = int(attrs.get("ks_ix", 2))# 2-2
params[LottieTensor.Index.Path.CLOSED] = 1.0 if attrs.get("closed", "true").lower() == "true" else 0.0
params[LottieTensor.Index.Path.HD] = 1.0 if attrs.get("hd", "false").lower() == "true" else 0.0 # Add HD
# Track if path is animated
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Path.ANIMATED] = 1.0
current_context = "path"
else:
params[LottieTensor.Index.Path.ANIMATED] = 0.0
elif cmd_idx == LottieTensor.CMD_POINT:
params[LottieTensor.Index.Point.X] = round(float(attrs.get("x", 0)))
params[LottieTensor.Index.Point.Y] = round(float(attrs.get("y", 0)))
params[LottieTensor.Index.Point.IN_X] = round(float(attrs.get("in_x", 0)))
params[LottieTensor.Index.Point.IN_Y] = round(float(attrs.get("in_y", 0)))
params[LottieTensor.Index.Point.OUT_X] = round(float(attrs.get("out_x", 0)))
params[LottieTensor.Index.Point.OUT_Y] = round(float(attrs.get("out_y", 0)))
elif cmd_idx == LottieTensor.CMD_FILL:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Fill")
# Check if color is animated
color_animated = attrs.get("color_animated", "false").lower() == "true"
params[LottieTensor.Index.Fill.COLOR_ANIMATED] = 1.0 if color_animated else 0.0
# Check if opacity is animated
opacity_animated = attrs.get("opacity_animated", "false").lower() == "true"
params[LottieTensor.Index.Fill.OPACITY_ANIMATED] = 1.0 if opacity_animated else 0.0
# Parse color keyframes if animated
if color_animated:
c_kf_count = int(attrs.get("c_kf_count", 0))
color_keyframes = []
for i in range(c_kf_count):
kf = {
't': round(float(attrs.get(f"c_kf_{i}_t", 0))),
'r': round(float(attrs.get(f"c_kf_{i}_r", 0))*255),
'g': round(float(attrs.get(f"c_kf_{i}_g", 0))*255),
'b': round(float(attrs.get(f"c_kf_{i}_b", 0))*255),
# Add easing parameters for color keyframes (multiply by 100 and round)
'i_x': round(float(attrs.get(f"c_kf_{i}_i_x", 0)) * 100),
'i_y': round(float(attrs.get(f"c_kf_{i}_i_y", 0)) * 100),
'o_x': round(float(attrs.get(f"c_kf_{i}_o_x", 0)) * 100),
'o_y': round(float(attrs.get(f"c_kf_{i}_o_y", 0)) * 100)
}
color_keyframes.append(kf)
# Store in string_params as JSON
#string_params[f"{cmd_key}_color_keyframes"] = json.dumps(color_keyframes)
else:
# Parse static color
params[LottieTensor.Index.Fill.R] = round(float(attrs.get("r", 0))*255)
params[LottieTensor.Index.Fill.G] = round(float(attrs.get("g", 0))*255)
params[LottieTensor.Index.Fill.B] = round(float(attrs.get("b", 0))*255)
# Parse opacity keyframes if animated
if opacity_animated:
o_kf_count = int(attrs.get("o_kf_count", 0))
opacity_keyframes = []
for i in range(o_kf_count):
kf = {
't': round(float(attrs.get(f"o_kf_{i}_t", 0))),
's': round(float(attrs.get(f"o_kf_{i}_s", 100))),
'i_x': round(float(attrs.get(f"o_kf_{i}_i_x", 0)) * 100),
'i_y': round(float(attrs.get(f"o_kf_{i}_i_y", 0)) * 100),
'o_x': round(float(attrs.get(f"o_kf_{i}_o_x", 0)) * 100),
'o_y': round(float(attrs.get(f"o_kf_{i}_o_y", 0)) * 100)
}
opacity_keyframes.append(kf)
# Store in string_params as JSON
#string_params[f"{cmd_key}_opacity_keyframes"] = json.dumps(opacity_keyframes)
else:
# Parse static opacity
params[LottieTensor.Index.Fill.OPACITY] = round(float(attrs.get("opacity", 100)))
# Parse other parameters
params[LottieTensor.Index.Fill.COLOR_DIM] = int(attrs.get("color_dim", 3))
params[LottieTensor.Index.Fill.HAS_C_A] = 1.0 if attrs.get("has_c_a", "").lower() == "true" else 0.0
params[LottieTensor.Index.Fill.HAS_C_IX] = 1.0 if attrs.get("has_c_ix", "").lower() == "true" else 0.0
params[LottieTensor.Index.Fill.C_IX] = int(attrs.get("c_ix", 4))
params[LottieTensor.Index.Fill.BM] = int(attrs.get("bm", 0))
params[LottieTensor.Index.Fill.FILL_RULE] = int(attrs.get("fill_rule", 1))
params[LottieTensor.Index.Fill.HAS_O_A] = 1.0 if attrs.get("has_o_a", "").lower() == "true" else 0.0
params[LottieTensor.Index.Fill.HAS_O_IX] = 1.0 if attrs.get("has_o_ix", "").lower() == "true" else 0.0
params[LottieTensor.Index.Fill.O_IX] = int(attrs.get("o_ix", 5))
elif cmd_idx == LottieTensor.CMD_BEZIER:
# Parse the closed attribute from input
closed_str = attrs.get("closed", "true")
params[LottieTensor.Index.Bezier.CLOSED] = 1.0 if closed_str.lower() == "true" else 0.0
#elif cmd_idx == LottieTensor.CMD_ELLIPSE:
#name = string_params.get(f"{cmd_key}_name", "Ellipse Path 1")
#lines.append(f'({cmd} name="{name}")')
elif cmd_idx in [LottieTensor.CMD_POSITION, LottieTensor.CMD_SIZE]:
if cmd == "size":
# Check if size is animated
if attrs.get("animated", "").lower() == "true":
# Don't parse values when animated
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
current_context = "size" # Set context for size keyframes
else:
# Explicitly set ANIMATED to 0.0 for static size
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# Parse two values for static size
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.Transform.X] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.Transform.Y] = round(float(value_parts[1]))
else:
# Handle position as before
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.TwoValues.VALUE1] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.TwoValues.VALUE2] = round(float(value_parts[1]))
elif cmd_idx == LottieTensor.CMD_RECT:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Rectangle Path 1")
params[LottieTensor.Index.Rect.HD] = 1.0 if attrs.get("hd", "false").lower() == "true" else 0.0
params[LottieTensor.Index.Rect.D] = int(attrs.get("d", 1))
elif cmd_idx == LottieTensor.CMD_ROUNDED:
# Parse rounded value and ix
rounded_val = round(float(attrs.get("rounded", attrs_str.split()[0] if attrs_str.split() else "0")))
params[LottieTensor.Index.SingleValue.VALUE] = rounded_val
params[LottieTensor.Index.SingleValue.IX] = int(attrs.get("ix", 4))
# ADD THIS NEW SECTION:
elif cmd_idx == LottieTensor.CMD_RECT_ROUNDED:
# Check if animated
if "animated" in attrs and attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.SingleValue.ANIMATED] = 1.0
current_context = "rect_rounded" # Set context for keyframes
else:
params[LottieTensor.Index.SingleValue.ANIMATED] = 0.0
# Parse the value if not animated
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_TRIM:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Trim Paths 1")
params[LottieTensor.Index.Trim.IX] = int(attrs.get("ix", 1))
elif cmd_idx == LottieTensor.CMD_END:
# Handle trim sub-commands
# Check if animated
if "animated" in attrs and attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.SingleValue.ANIMATED] = 1.0
current_context = "trim_end" # Set context for keyframes
# Don't parse value when animated=true
else:
params[LottieTensor.Index.SingleValue.ANIMATED] = 0.0
# Parse the value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_START:
# Handle trim sub-commands
# Check if animated
if "animated" in attrs and attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.SingleValue.ANIMATED] = 1.0
current_context = "trim_start" # Set context for keyframes
# Don't parse value when animated=true
else:
params[LottieTensor.Index.SingleValue.ANIMATED] = 0.0
# Parse the value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_OFFSET:
# Handle trim sub-commands
# Check if animated
if "animated" in attrs and attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.SingleValue.ANIMATED] = 1.0
current_context = "trim_offset" # Set context for keyframes
# Don't parse value when animated=true
else:
params[LottieTensor.Index.SingleValue.ANIMATED] = 0.0
# Parse the value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_MULTIPLE:
# Parse multiple value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_REPEATER:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Repeater 1")
params[LottieTensor.Index.Repeater.IX] = int(attrs.get("ix", 1))
elif cmd_idx == LottieTensor.CMD_COPIES:
# Parse copies value and ix
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
params[LottieTensor.Index.SingleValue.IX] = int(attrs.get("ix", 1))
elif cmd_idx == LottieTensor.CMD_REPEATER_OFFSET:
# Parse repeater_offset value and ix
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
params[LottieTensor.Index.SingleValue.IX] = int(attrs.get("ix", 2))
elif cmd_idx == LottieTensor.CMD_COMPOSITE:
# Parse composite value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_TR_SCALE:
# Parse tr_scale values
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.TwoValues.VALUE1] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.TwoValues.VALUE2] = round(float(value_parts[1]))
elif cmd_idx in [LottieTensor.CMD_TR_P_IX, LottieTensor.CMD_TR_A_IX, LottieTensor.CMD_TR_S_IX,
LottieTensor.CMD_TR_R_IX, LottieTensor.CMD_TR_SO_IX, LottieTensor.CMD_TR_EO_IX]:
# Parse single index value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_TRANSFORM_SHAPE:
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Transform")
# Parse hd attribute
params[LottieTensor.Index.TransformShape.HD] = 1.0 if attrs.get("hd", "false").lower() == "true" else 0.0
# Parse position
position_str = attrs.get("position", "0 0").strip('"')
pos_parts = position_str.split()
if len(pos_parts) >= 1:
params[LottieTensor.Index.TransformShape.POSITION_X] = round(float(pos_parts[0]))
if len(pos_parts) >= 2:
params[LottieTensor.Index.TransformShape.POSITION_Y] = round(float(pos_parts[1]))
# Parse scale
scale_str = attrs.get("scale", "100 100").strip('"')
scale_parts = scale_str.split()
if len(scale_parts) >= 1:
params[LottieTensor.Index.TransformShape.SCALE_X] = round(float(scale_parts[0]))
if len(scale_parts) >= 2:
params[LottieTensor.Index.TransformShape.SCALE_Y] = round(float(scale_parts[1]))
# Parse rotation
rotation_str = attrs.get("rotation", "0").strip('"')
params[LottieTensor.Index.TransformShape.ROTATION] = round(float(rotation_str))
# Parse opacity
opacity_str = attrs.get("opacity", "100").strip('"')
params[LottieTensor.Index.TransformShape.OPACITY] = round(float(opacity_str))
# Parse anchor
anchor_str = attrs.get("anchor", "0 0").strip('"')
anchor_parts = anchor_str.split()
if len(anchor_parts) >= 1:
params[LottieTensor.Index.TransformShape.ANCHOR_X] = round(float(anchor_parts[0]))
if len(anchor_parts) >= 2:
params[LottieTensor.Index.TransformShape.ANCHOR_Y] = round(float(anchor_parts[1]))
# Parse skew (only if present)
if "skew" in attrs:
skew_str = attrs.get("skew", "0").strip('"')
params[LottieTensor.Index.TransformShape.SKEW] = round(float(skew_str))
# Parse skew_axis (only if present)
if "skew_axis" in attrs:
skew_axis_str = attrs.get("skew_axis", "0").strip('"')
params[LottieTensor.Index.TransformShape.SKEW_AXIS] = round(float(skew_axis_str))
elif cmd_idx == LottieTensor.CMD_PARENT:
# Parse parent index
parent_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
parent_parts.append(part)
except ValueError:
pass
if parent_parts:
params[LottieTensor.Index.Parent.PARENT_INDEX] = round(float(parent_parts[0]))
elif cmd_idx == LottieTensor.CMD_ASSET:
if LottieTensor.tokenizer is None:
LottieTensor.init_tokenizer()
asset_id = attrs.get("id", "comp_0")
id_tokens = LottieTensor.tokenizer.encode(asset_id, add_special_tokens=False)[:10] # Limit to 10 tokens
for i, token_id in enumerate(id_tokens):
if i < 10:
params[LottieTensor.Index.Asset.ID_TOKEN_0 + i] = int(token_id)
params[LottieTensor.Index.Asset.ID_TOKEN_COUNT] = int(len(id_tokens))
#string_params[f"{cmd_key}_id"] = attrs.get("id", "comp_0")
#string_params[f"{cmd_key}_nm"] = attrs.get("nm", "asset")
params[LottieTensor.Index.Asset.FR] = round(float(attrs.get("fr", 30)))
elif cmd_idx == LottieTensor.CMD_FONT:
# 存储字符串参数
#string_params[f"{cmd_key}_family"] = attrs.get("family", "")
#string_params[f"{cmd_key}_style"] = attrs.get("style", "")
if LottieTensor.tokenizer is None:
LottieTensor.init_tokenizer()
# 编码family和style
family = attrs.get("family", "")
style = attrs.get("style", "")
family_tokens = LottieTensor.tokenizer.encode(family, add_special_tokens=False)[:10]
style_tokens = LottieTensor.tokenizer.encode(style, add_special_tokens=False)[:10]
params[LottieTensor.Index.Font.ASCENT] = round(float(attrs.get("ascent", 75)))
for i, token_id in enumerate(family_tokens):
if i < 10:
params[LottieTensor.Index.Font.FAMILY_TOKEN_0 + i] = int(token_id)
params[LottieTensor.Index.Font.FAMILY_TOKEN_COUNT] = int(len(family_tokens))
# Store style tokens
for i, token_id in enumerate(style_tokens):
if i < 10:
params[LottieTensor.Index.Font.STYLE_TOKEN_0 + i] = int(token_id)
params[LottieTensor.Index.Font.STYLE_TOKEN_COUNT] = int(len(style_tokens))
elif cmd_idx == LottieTensor.CMD_CHAR:
if LottieTensor.tokenizer is None:
LottieTensor.init_tokenizer()
# 特殊处理 ch 属性,因为可能包含特殊字符如 ")"
ch_match = re.search(r'ch="([^"]*)"', attrs_str)
ch = ch_match.group(1) if ch_match else attrs.get("ch", "")
# 解析其他属性时,需要先移除 ch 属性以避免干扰
temp_attrs_str = attrs_str
if ch_match:
temp_attrs_str = attrs_str[:ch_match.start()] + attrs_str[ch_match.end():]
# 重新解析其他属性
temp_attrs = LottieTensor._parse_attributes(temp_attrs_str)
style = temp_attrs.get("style", "")
family = temp_attrs.get("family", "")
# Encode strings
ch_tokens = LottieTensor.tokenizer.encode(ch, add_special_tokens=False)[:10]
style_tokens = LottieTensor.tokenizer.encode(style, add_special_tokens=False)[:10]
family_tokens = LottieTensor.tokenizer.encode(family, add_special_tokens=False)[:10]
params[LottieTensor.Index.Char.SIZE] = round(float(temp_attrs.get("size", 100)))
params[LottieTensor.Index.Char.W] = round(float(temp_attrs.get("w", 0)))
# Store ch tokens
for i, token_id in enumerate(ch_tokens):
if i < 10:
params[LottieTensor.Index.Char.CH_TOKEN_0 + i] = int(token_id)
params[LottieTensor.Index.Char.CH_TOKEN_COUNT] = int(len(ch_tokens))
# Store style tokens
for i, token_id in enumerate(style_tokens):
if i < 10:
params[LottieTensor.Index.Char.STYLE_TOKEN_0 + i] = int(token_id)
params[LottieTensor.Index.Char.STYLE_TOKEN_COUNT] = int(len(style_tokens))
# Store family tokens
for i, token_id in enumerate(family_tokens):
if i < 10:
params[LottieTensor.Index.Char.FAMILY_TOKEN_0 + i] = int(token_id)
params[LottieTensor.Index.Char.FAMILY_TOKEN_COUNT] = int(len(family_tokens))
elif cmd_idx == LottieTensor.CMD_FONT_SIZE:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.FontSize.SIZE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_FONT_FAMILY:
# 提取引号内的字体族名称
family_match = re.search(r'"([^"]*)"', attrs_str)
if family_match:
string_params[f"{cmd_key}_family"] = family_match.group(1)
elif cmd_idx == LottieTensor.CMD_TEXT:
# 提取引号内的文本
text_match = re.search(r'"([^"]*)"', attrs_str)
if text_match:
string_params[f"{cmd_key}_text"] = text_match.group(1)
elif cmd_idx == LottieTensor.CMD_CA:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Ca.VALUE] = int(value_parts[0])
elif cmd_idx == LottieTensor.CMD_JUSTIFY:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Justify.VALUE] = int(value_parts[0])
elif cmd_idx == LottieTensor.CMD_TRACKING:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Tracking.VALUE] = int(value_parts[0])
elif cmd_idx == LottieTensor.CMD_LINE_HEIGHT:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.LineHeight.VALUE] = int(value_parts[0])
elif cmd_idx == LottieTensor.CMD_LETTER_SPACING:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.LetterSpacing.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_FILL_COLOR:
# 提取RGB值
value_parts = []
for part in attrs_str.split():
try:
round((part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.FillColor.R] = round(float(value_parts[0]) * 255)
if len(value_parts) >= 2:
params[LottieTensor.Index.FillColor.G] = round(float(value_parts[1]) * 255)
if len(value_parts) >= 3:
params[LottieTensor.Index.FillColor.B] = round(float(value_parts[2]) * 255)
elif cmd_idx == LottieTensor.CMD_G:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.G.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_ALIGNMENT:
params[LottieTensor.Index.Alignment.A] = round(float(attrs.get("a", 0)))
elif cmd_idx == LottieTensor.CMD_ALIGNMENT_K:
# 提取两个数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.AlignmentK.VALUE1] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.AlignmentK.VALUE2] = round(float(value_parts[1]))
elif cmd_idx == LottieTensor.CMD_ALIGNMENT_IX:
# 提取数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.AlignmentIx.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_EFFECTS:
# effects容器命令,不需要参数
pass
elif cmd_idx == LottieTensor.CMD_EFFECT:
# Store string parameters
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
#string_params[f"{cmd_key}_match_name"] = attrs.get("match_name", "")
# Store numeric parameters
params[LottieTensor.Index.Effect.TYPE] = int(attrs.get("type", 0))
params[LottieTensor.Index.Effect.INDEX] = round(float(attrs.get("index", 1)))
params[LottieTensor.Index.Effect.NP] = int(attrs.get("np", 0)) # Add NP
params[LottieTensor.Index.Effect.ENABLED] = round(float(attrs.get("enabled", 1))) # Add ENABLED
# Add CMD_LAYER_EFFECT parsing:
elif cmd_idx == LottieTensor.CMD_LAYER_EFFECT:
# Store string parameters
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
#string_params[f"{cmd_key}_match_name"] = attrs.get("match_name", "")
# Store numeric parameters
params[LottieTensor.Index.LayerEffect.INDEX] = round(float(attrs.get("index", 1)))
params[LottieTensor.Index.LayerEffect.VALUE] = round(float(attrs.get("value", 0)))
elif cmd_idx == LottieTensor.CMD_DROPDOWN:
# 存储字符串参数
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
# 存储数值参数
params[LottieTensor.Index.Dropdown.INDEX] = round(float(attrs.get("index", 1)))
params[LottieTensor.Index.Dropdown.VALUE] = round(float(attrs.get("value", 0)))
elif cmd_idx == LottieTensor.CMD_NO_VALUE:
# 存储字符串参数
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
# 存储数值参数
params[LottieTensor.Index.NO_VALUE.INDEX] = round(float(attrs.get("index", 1)))
params[LottieTensor.Index.NO_VALUE.VALUE] = round(float(attrs.get("value", 0)))
elif cmd_idx == LottieTensor.CMD_IGNORED:
# 存储字符串参数
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
# 存储数值参数
params[LottieTensor.Index.Ignored.INDEX] = round(float(attrs.get("index", 1)))
params[LottieTensor.Index.Ignored.VALUE] = round(float(attrs.get("value", 0)))
elif cmd_idx == LottieTensor.CMD_SLIDER:
# 存储字符串参数
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
# 存储数值参数
params[LottieTensor.Index.Slider.INDEX] = round(float(attrs.get("index", 1)))
params[LottieTensor.Index.Slider.VALUE] = round(float(attrs.get("value", 0)))
elif cmd_idx == LottieTensor.CMD_GRADIENT_FILL:
# Store name only for gradient_fill command
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Gradient Fill 1")
# Set context for subsequent commands
current_context = "gradient_fill"
elif cmd_idx == LottieTensor.CMD_OPACITY and current_context == "gradient_fill":
# Parse opacity value for gradient_fill context
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_FILL_RULE:
# Parse fill_rule value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_START_POINT:
# Parse start_point values
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.TwoValues.VALUE1] = LottieTensor._clamp_value(round(float(value_parts[0])))
if len(value_parts) >= 2:
params[LottieTensor.Index.TwoValues.VALUE2] = LottieTensor._clamp_value(round(float(value_parts[1])))
elif cmd_idx == LottieTensor.CMD_END_POINT:
# Parse end_point values
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.TwoValues.VALUE1] = LottieTensor._clamp_value(round(float(value_parts[0])))
if len(value_parts) >= 2:
params[LottieTensor.Index.TwoValues.VALUE2] = LottieTensor._clamp_value(round(float(value_parts[1])))
elif cmd_idx == LottieTensor.CMD_GRADIENT_TYPE:
# Parse gradient_type value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_STAR:
# 解析 star 命令的属性
#string_params[f"{cmd_key}_name"] = attrs.get("name", "None")
# 解析 d 和 sy 参数
params[LottieTensor.Index.Star.D] = round(float(attrs.get("d", 1)))
params[LottieTensor.Index.Star.SY] = round(float(attrs.get("sy", 1)))
# IX 参数如果存在的话
#params[LottieTensor.Index.Star.IX] = float(attrs.get("ix", 1))
# 3. 添加 inner_radius 等子命令的解析
elif cmd_idx == LottieTensor.CMD_INNER_RADIUS:
# 解析数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_OUTER_RADIUS:
# 解析数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_INNER_ROUNDNESS:
# 解析数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_OUTER_ROUNDNESS:
# 解析数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_POINTS_STAR: # 这应该是 points_star
# 解析数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_STAR_ROTATION:
# 解析数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_HIGHLIGHT_LENGTH:
# Parse highlight_length value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_HIGHLIGHT_ANGLE:
# Parse highlight_angle value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_ORIGINAL_COLORS:
colors_match = re.search(r'\[([^\]]*)\]', attrs_str)
if colors_match:
colors_str = colors_match.group(1)
color_values = []
for color_part in colors_str.split(','):
try:
color_values.append(round(float(color_part.strip())*255))
except ValueError:
color_values.append(0)
# Store up to 24 color values (increased from 18)
for i, val in enumerate(color_values[:48]):
if i < 48: # Make sure we don't exceed our storage capacity
params[LottieTensor.Index.OriginalColors.COLOR_0 + i] = val
# Store count of colors
params[LottieTensor.Index.OriginalColors.COUNT] = int(len(color_values))
elif cmd_idx == LottieTensor.CMD_COLOR_POINTS:
# Parse color_points value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_GRADIENT_FILL_END:
# Reset context when gradient_fill ends
current_context = None
elif cmd_idx == LottieTensor.CMD_GRADIENT_STROKE:
# Store name only - similar to gradient_fill
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Gradient Stroke 1")
# Set context for subsequent commands
current_context = "gradient_stroke"
# Add handling for individual gradient_stroke sub-commands:
elif cmd_idx == LottieTensor.CMD_OPACITY and current_context == "gradient_stroke":
# Parse opacity value for gradient_stroke context
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_WIDTH:
value_parts = []
for part in attrs_str.split():
try:
float(part) # 只检查是否是数字,不用round
value_parts.append(part)
except ValueError:
pass
if value_parts:
# 乘以100保留小数精度
value = round(float(value_parts[0]) * 10)
value = max(0, min(10000, value)) # 裁剪
params[LottieTensor.Index.SingleValue.VALUE] = value
elif cmd_idx == LottieTensor.CMD_LINE_CAP:
# Parse line_cap value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_LINE_JOIN:
# Parse line_join value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_MITER_LIMIT:
# Parse miter_limit value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_GRADIENT_STROKE_END:
# Reset context when gradient_stroke ends
current_context = None
elif cmd_idx == LottieTensor.CMD_COLOR:
# Store string parameters
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Color")
# Store numeric parameters
params[LottieTensor.Index.Color.INDEX] = round(float(attrs.get("index", 1)))
params[LottieTensor.Index.Color.R] = round(float(attrs.get("r", 0))*255)
params[LottieTensor.Index.Color.G] = round(float(attrs.get("g", 0))*255)
params[LottieTensor.Index.Color.B] = round(float(attrs.get("b", 0))*255)
#elif cmd_idx == LottieTensor.CMD_MERGE:
# 存储merge的name属性
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Merge Paths 1")
elif cmd_idx == LottieTensor.CMD_MERGE_MODE:
# 解析merge_mode的数值
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.MergeMode.MODE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_MASK:
# Store string attributes
#string_params[f"{cmd_key}_nm"] = attrs.get("nm", "Mask")
params[LottieTensor.Index.Mask.INDEX] = round(float(attrs.get("index", 0)))
params[LottieTensor.Index.Mask.INV] = 1.0 if attrs.get("inv", "false").lower() == "true" else 0.0
# Parse mode attribute
mode = attrs.get("mode", "a")
# Convert mode to numeric for storage
mode_map = {"a": 0, "s": 1, "i": 2, "n": 3}
mode_val = mode_map.get(mode, 0)
params[LottieTensor.Index.Mask.MODE] = round(float(mode_val))
elif cmd_idx == LottieTensor.CMD_MASK_PT:
params[LottieTensor.Index.MaskPt.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.MaskPt.IX] = int(attrs.get("ix", 1))
elif cmd_idx == LottieTensor.CMD_MASK_PT_K_C:
# Parse c (closed) attribute - handle both formats
if "true" in attrs_str.lower():
params[LottieTensor.Index.MaskPtK.C] = 1.0
elif "false" in attrs_str.lower():
params[LottieTensor.Index.MaskPtK.C] = 0.0
else:
# Try to parse from c= format if present
c_str = attrs.get("c", "true")
params[LottieTensor.Index.MaskPtK.C] = 1.0 if c_str.lower() == "true" else 0.0
elif cmd_idx in [LottieTensor.CMD_MASK_PT_K_I, LottieTensor.CMD_MASK_PT_K_O, LottieTensor.CMD_MASK_PT_K_V]:
# Parse all numeric values from the line
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
# Store up to 20 values (increased from 8)
for i in range(min(20, len(value_parts))):
if i < LottieTensor.PARAM_DIM: # Make sure we don't exceed param dimension
params[LottieTensor.Index.MaskPtKValues.V1 + i] = round(float(value_parts[i]))
# Store the count of values in string_params for reconstruction
params[LottieTensor.Index.MaskPtKValues.COUNT] = round(float(len(value_parts)))
elif cmd_idx == LottieTensor.CMD_MASK_O:
params[LottieTensor.Index.MaskO.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.MaskO.K] = round(float(attrs.get("k", 100)))
params[LottieTensor.Index.MaskO.IX] = int(attrs.get("ix", 3))
elif cmd_idx == LottieTensor.CMD_MASK_X:
params[LottieTensor.Index.MaskX.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.MaskX.K] = round(float(attrs.get("k", 0)))
params[LottieTensor.Index.MaskX.IX] = int(attrs.get("ix", 4))
# 添加这部分:检查是否是动画
if float(attrs.get("a", 0)) > 0.5:
current_context = "mask_x"
elif cmd_idx == LottieTensor.CMD_MASKS_PROPERTIES:
# Container command, no parameters needed
pass
elif cmd_idx in [LottieTensor.CMD_MASK_PT_K_ARRAY, LottieTensor.CMD_MASK_PT_K_ARRAY_END,
LottieTensor.CMD_MASK_PT_KF_S, LottieTensor.CMD_MASK_PT_KF_S_END,
LottieTensor.CMD_MASK_PT_KF_SHAPE_END, LottieTensor.CMD_MASK_PT_KEYFRAME_END]:
# Container commands, no parameters
pass
elif cmd_idx == LottieTensor.CMD_MASK_PT_KEYFRAME:
params[LottieTensor.Index.MaskPtKeyframe.INDEX] = int(attrs.get("index", 0))
params[LottieTensor.Index.MaskPtKeyframe.T] = round(float(attrs.get("t", 0)))
elif cmd_idx == LottieTensor.CMD_MASK_PT_KF_I:
params[LottieTensor.Index.MaskPtKfI.X] = round(float(attrs.get("x", 0)))
params[LottieTensor.Index.MaskPtKfI.Y] = round(float(attrs.get("y", 0)))
elif cmd_idx == LottieTensor.CMD_MASK_PT_KF_O:
params[LottieTensor.Index.MaskPtKfO.X] = round(float(attrs.get("x", 0)))
params[LottieTensor.Index.MaskPtKfO.Y] = round(float(attrs.get("y", 0)))
elif cmd_idx == LottieTensor.CMD_MASK_PT_KF_SHAPE:
params[LottieTensor.Index.MaskPtKfShape.INDEX] = int(attrs.get("index", 0))
c_str = attrs.get("c", "true")
params[LottieTensor.Index.MaskPtKfShape.C] = 1.0 if c_str.lower() == "true" else 0.0
elif cmd_idx in [LottieTensor.CMD_MASK_PT_KF_SHAPE_I, LottieTensor.CMD_MASK_PT_KF_SHAPE_O,
LottieTensor.CMD_MASK_PT_KF_SHAPE_V]:
# Parse all numeric values from the line
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
# Store up to 20 values (increased from 8)
for i in range(min(20, len(value_parts))):
if i < LottieTensor.PARAM_DIM: # Make sure we don't exceed param dimension
params[LottieTensor.Index.MaskPtKfShapeValues.V1 + i] = round(float(value_parts[i]))
# Store the count in params, NOT in string_params
params[LottieTensor.Index.MaskPtKfShapeValues.COUNT] = round(float(len(value_parts)))
elif cmd_idx == LottieTensor.CMD_TR_POSITION:
# Parse tr_position values
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.TrPosition.X] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.TrPosition.Y] = round(float(value_parts[1]))
elif cmd_idx == LottieTensor.CMD_TR_ANCHOR:
# Parse tr_anchor values
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.TrAnchor.X] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.TrAnchor.Y] = round(float(value_parts[1]))
elif cmd_idx == LottieTensor.CMD_TR_ROTATION:
# Parse tr_rotation value
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.TrRotation.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_TR_START_OPACITY:
# Parse tr_start_opacity value
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.TrStartOpacity.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_TR_END_OPACITY:
# Parse tr_end_opacity value
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.TrEndOpacity.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_ZIG_ZAG:
# Store string parameters
#string_params[f"{cmd_key}_name"] = attrs.get("name", "Zig Zag 1")
# Store numeric parameters
params[LottieTensor.Index.ZigZag.IX] = int(attrs.get("ix", 2))
elif cmd_idx == LottieTensor.CMD_FREQUENCY:
# Parse frequency value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Frequency.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_AMPLITUDE:
# Parse amplitude value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Amplitude.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_POINT_TYPE:
# Parse point_type value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.PointType.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_ANIMATORS:
# Container command, no parameters
pass
#elif cmd_idx == LottieTensor.CMD_ANIMATOR:
# Store animator name
#string_params[f"{cmd_key}_nm"] = attrs.get("nm", "Animator 1")
elif cmd_idx == LottieTensor.CMD_RANGE_SELECTOR:
params[LottieTensor.Index.RangeSelector.T] = round(float(attrs.get("t", 0)))
params[LottieTensor.Index.RangeSelector.R] = round(float(attrs.get("r", 1)))
params[LottieTensor.Index.RangeSelector.B] = round(float(attrs.get("b", 1)))
params[LottieTensor.Index.RangeSelector.SH] = round(float(attrs.get("sh", 1))) #这里还需要check下
params[LottieTensor.Index.RangeSelector.RN] = round(float(attrs.get("rn", 0)))
elif cmd_idx == LottieTensor.CMD_RANGE_START:
params[LottieTensor.Index.RangeStart.A] = round(float(attrs.get("a", 0)))
# Check if animated
if float(attrs.get("a", 0)) > 0.5:
current_context = "range_start"
elif cmd_idx == LottieTensor.CMD_RANGE_START_KEYFRAME:
params[LottieTensor.Index.RangeStartKeyframe.T] = round(float(attrs.get("t", 0)))
params[LottieTensor.Index.RangeStartKeyframe.S] = round(float(attrs.get("s", 0)))
params[LottieTensor.Index.RangeStartKeyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0"))*100))
params[LottieTensor.Index.RangeStartKeyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0"))*100))
params[LottieTensor.Index.RangeStartKeyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0"))*100))
params[LottieTensor.Index.RangeStartKeyframe.O_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0"))*100))
elif cmd_idx == LottieTensor.CMD_AMOUNT:
params[LottieTensor.Index.Amount.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.Amount.K] = round(float(attrs.get("k", 100)))
params[LottieTensor.Index.Amount.IX] = int(attrs.get("ix", 4))
elif cmd_idx == LottieTensor.CMD_MAX_EASE:
params[LottieTensor.Index.MaxEase.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.MaxEase.K] = round(float(attrs.get("k", 0)))
params[LottieTensor.Index.MaxEase.IX] = int(attrs.get("ix", 7))
elif cmd_idx == LottieTensor.CMD_MIN_EASE:
params[LottieTensor.Index.MinEase.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.MinEase.K] = round(float(attrs.get("k", 0)))
params[LottieTensor.Index.MinEase.IX] = int(attrs.get("ix", 8))
elif cmd_idx == LottieTensor.CMD_ANIMATOR_PROPERTIES:
# Container command, no parameters
current_context = "animator_properties" # Set context for opacity handling
pass
elif cmd_idx == LottieTensor.CMD_ANIMATOR_PROPERTIES_END:
current_context = None # Reset context
pass
elif cmd_idx == LottieTensor.CMD_OPACITY and current_context == "animator_properties":
# Special handling for opacity within animator_properties
params[LottieTensor.Index.Amount.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.Amount.K] = round(float(attrs.get("k", 0)))
params[LottieTensor.Index.Amount.IX] = int(attrs.get("ix", 9))
elif cmd_idx == LottieTensor.CMD_RADIUS:
# Parse radius value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.Radius.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_RANGE_END:
params[LottieTensor.Index.RangeEnd.A] = round(float(attrs.get("a", 0)))
# Check if animated
if float(attrs.get("a", 0)) > 0.5:
current_context = "range_end"
elif cmd_idx == LottieTensor.CMD_RANGE_END_KEYFRAME:
params[LottieTensor.Index.RangeEndKeyframe.T] = round(float(attrs.get("t", 0)))
params[LottieTensor.Index.RangeEndKeyframe.S] = round(float(attrs.get("s", 0)))
params[LottieTensor.Index.RangeEndKeyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0"))*100))
params[LottieTensor.Index.RangeEndKeyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0"))*100))
params[LottieTensor.Index.RangeEndKeyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0"))*100))
params[LottieTensor.Index.RangeEndKeyframe.O_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0"))*100))
elif cmd_idx == LottieTensor.CMD_POSITION and current_context == "animator_properties":
# Special handling for position within animator_properties
params[LottieTensor.Index.Amount.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.Amount.IX] = int(attrs.get("ix", 2))
# Parse k parameter which can be an array
k_str = attrs.get("k", "0")
if k_str.startswith("[") and k_str.endswith("]"):
# Parse array values
k_str = k_str[1:-1] # Remove brackets
k_parts = k_str.split(",")
if len(k_parts) >= 1:
params[LottieTensor.Index.Transform.X] = round(float(k_parts[0].strip()))
if len(k_parts) >= 2:
params[LottieTensor.Index.Transform.Y] = round(float(k_parts[1].strip()))
if len(k_parts) >= 3:
params[LottieTensor.Index.Transform.Z] = round(float(k_parts[2].strip()))
else:
params[LottieTensor.Index.Amount.K] = round(float(k_str))
elif cmd_idx == LottieTensor.CMD_ML2:
# Parse ml2 value
value_parts = []
for part in attrs_str.split():
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if value_parts:
params[LottieTensor.Index.SingleValue.VALUE] = round(float(value_parts[0]))
elif cmd_idx == LottieTensor.CMD_RANGE_OFFSET_KEYFRAME:
params[LottieTensor.Index.RangeOffsetKeyframe.T] = round(float(attrs.get("t", 0)))
params[LottieTensor.Index.RangeOffsetKeyframe.S] = round(float(attrs.get("s", 0)))
params[LottieTensor.Index.RangeOffsetKeyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0"))*100))
params[LottieTensor.Index.RangeOffsetKeyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0"))*100))
params[LottieTensor.Index.RangeOffsetKeyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0"))*100))
params[LottieTensor.Index.RangeOffsetKeyframe.O_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0"))*100))
elif cmd_idx == LottieTensor.CMD_S_M:
params[LottieTensor.Index.SM.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.SM.K] = round(float(attrs.get("k", 100)))
params[LottieTensor.Index.SM.IX] = int(attrs.get("ix", 6))
elif cmd_idx == LottieTensor.CMD_OPACITY_ANIMATORS:
params[LottieTensor.Index.OpacityAnimators.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.OpacityAnimators.IX] = int(attrs.get("ix", 9))
# If not animated (a=0), parse k value
if float(attrs.get("a", 0)) < 0.5:
params[LottieTensor.Index.OpacityAnimators.K] = round(float(attrs.get("k", 0)))
else:
# Set context for animated keyframes
current_context = "opacity_animators"
elif cmd_idx == LottieTensor.CMD_POSITION_ANIMATORS_END:
current_context = None
# 添加 CMD_POSITION_ANIMATORS 处理:
elif cmd_idx == LottieTensor.CMD_POSITION_ANIMATORS:
params[LottieTensor.Index.PositionAnimators.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.PositionAnimators.IX] = int(attrs.get("ix", 2))
# If not animated (a=0), parse k value
if float(attrs.get("a", 0)) < 0.5:
# Parse k value which could be a single value or array
k_str = attrs.get("k", "0")
if k_str.startswith("[") and k_str.endswith("]"):
# Parse array values
k_str = k_str[1:-1] # Remove brackets
k_parts = k_str.split(",")
if len(k_parts) >= 1:
params[LottieTensor.Index.PositionAnimators.K_X] = round(float(k_parts[0].strip()))
if len(k_parts) >= 2:
params[LottieTensor.Index.PositionAnimators.K_Y] = round(float(k_parts[1].strip()))
if len(k_parts) >= 3:
params[LottieTensor.Index.PositionAnimators.K_Z] = round(float(k_parts[2].strip()))
else:
# Single value - apply to all dimensions
try:
k_val = round(float(k_str))
params[LottieTensor.Index.PositionAnimators.K_X] = k_val
params[LottieTensor.Index.PositionAnimators.K_Y] = k_val
params[LottieTensor.Index.PositionAnimators.K_Z] = k_val
except ValueError:
# Default to 0 if parsing fails
params[LottieTensor.Index.PositionAnimators.K_X] = 0
params[LottieTensor.Index.PositionAnimators.K_Y] = 0
params[LottieTensor.Index.PositionAnimators.K_Z] = 0
else:
# Set context for animated keyframes
current_context = "position_animators"
# 添加 CMD_TRACKING_ANIMATORS 处理:
elif cmd_idx == LottieTensor.CMD_TRACKING_ANIMATORS:
params[LottieTensor.Index.TrackingAnimators.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.TrackingAnimators.K] = round(float(attrs.get("k", 0)))
params[LottieTensor.Index.TrackingAnimators.IX] = int(attrs.get("ix", 89))
# If animated, set context
if float(attrs.get("a", 0)) > 0.5:
current_context = "tracking_animators"
elif cmd_idx == LottieTensor.CMD_SCALE_ANIMATORS:
params[LottieTensor.Index.ScaleAnimators.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.ScaleAnimators.IX] = int(attrs.get("ix", 3))
# If not animated (a=0), parse k value
if float(attrs.get("a", 0)) < 0.5:
# Parse k value which could be a single value or array
k_str = attrs.get("k", "100")
if k_str.startswith("[") and k_str.endswith("]"):
# Parse array values
k_str = k_str[1:-1] # Remove brackets
k_parts = k_str.split(",")
if len(k_parts) >= 1:
params[LottieTensor.Index.ScaleAnimators.K_X] = round(float(k_parts[0].strip()))
if len(k_parts) >= 2:
params[LottieTensor.Index.ScaleAnimators.K_Y] = round(float(k_parts[1].strip()))
if len(k_parts) >= 3:
params[LottieTensor.Index.ScaleAnimators.K_Z] = round(float(k_parts[2].strip()))
else:
# Single value - apply to all dimensions
k_val = round(float(k_str))
params[LottieTensor.Index.ScaleAnimators.K_X] = k_val
params[LottieTensor.Index.ScaleAnimators.K_Y] = k_val
params[LottieTensor.Index.ScaleAnimators.K_Z] = k_val
else:
# Set context for animated keyframes
current_context = "scale_animators"
elif cmd_idx == LottieTensor.CMD_ROTATION_ANIMATORS:
params[LottieTensor.Index.RotationAnimators.A] = int(attrs.get("a", 0))
params[LottieTensor.Index.RotationAnimators.IX] = int(attrs.get("ix", 4))
# If not animated (a=0), parse k value
if float(attrs.get("a", 0)) < 0.5:
params[LottieTensor.Index.RotationAnimators.K] = int(attrs.get("k", 0))
else:
# Set context for animated keyframes
current_context = "rotation_animators"
elif cmd_idx == LottieTensor.CMD_SCALE_ANIMATORS_END:
current_context = None
elif cmd_idx == LottieTensor.CMD_ROTATION_ANIMATORS_END:
current_context = None
elif cmd_idx == LottieTensor.CMD_RANGE_OFFSET:
# Parse the 'a' attribute correctly
params[LottieTensor.Index.Amount.A] = int(attrs.get("a", 0))
# Check if animated
if float(attrs.get("a", 0)) > 0.5:
# Animated case - set context for keyframes
current_context = "range_offset"
else:
# Static case - also parse k and ix values
params[LottieTensor.Index.Amount.K] = int(attrs.get("k", 0))
params[LottieTensor.Index.Amount.IX] = int(attrs.get("ix", 3))
elif cmd_idx == LottieTensor.CMD_DASHES:
# Container command for dashes
# Parse the entire dashes string if present
dashes_str = attrs_str.strip()
if dashes_str:
string_params[f"{cmd_key}_dashes"] = dashes_str
elif cmd_idx == LottieTensor.CMD_DASH:
dash_type = attrs.get("type", "d")
type_map = {"d": 0, "g": 1, "o": 2}
params[LottieTensor.Index.Dash.TYPE] = int(type_map.get(dash_type, 0))
# 乘以100保留小数精度
length_val = round(float(attrs.get("length", 0)) * 10)
length_val = max(0, min(10000, length_val))
params[LottieTensor.Index.Dash.LENGTH] = length_val
params[LottieTensor.Index.Dash.V_IX] = int(attrs.get("v_ix", 1))
# Store name in string_params
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
elif cmd_idx == LottieTensor.CMD_DASH_ANIMATED:
# Parse dash_animated attributes
dash_type = attrs.get("type", "o")
# Convert type to numeric
type_map = {"d": 0, "g": 1, "o": 2}
params[LottieTensor.Index.DashAnimated.TYPE] = int(type_map.get(dash_type, 2))
# Parse v_ix
params[LottieTensor.Index.DashAnimated.V_IX] = int(attrs.get("v_ix", 7))
# Store name in string_params
#string_params[f"{cmd_key}_name"] = attrs.get("name", "")
# Set context for keyframes
current_context = "dash_animated"
elif cmd_idx == LottieTensor.CMD_DASH_KEYFRAME:
params[LottieTensor.Index.DashKeyframe.T] = round(float(attrs.get("t", 0)))
# 改成乘以10,并裁剪
s_val = round(float(attrs.get("s", 0)) * 10)
s_val = max(0, min(10000, s_val))
params[LottieTensor.Index.DashKeyframe.S] = s_val
# easing参数保持不变
params[LottieTensor.Index.DashKeyframe.I_X] = round(float(LottieTensor._parse_easing_value(attrs.get("i_x", "0"))*100))
params[LottieTensor.Index.DashKeyframe.I_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("i_y", "0"))*100))
params[LottieTensor.Index.DashKeyframe.O_X] = round(float(LottieTensor._parse_easing_value(attrs.get("o_x", "0"))*100))
params[LottieTensor.Index.DashKeyframe.O_Y] = round(float(LottieTensor._parse_easing_value(attrs.get("o_y", "0"))*100))
elif cmd_idx == LottieTensor.CMD_DASH_ANIMATED_END:
# Reset context
current_context = None
elif cmd_idx == LottieTensor.CMD_DASH_OFFSET:
value_parts = []
for part in attrs_str.split():
try:
float(part)
value_parts.append(part)
except ValueError:
pass
if value_parts:
# 改成乘以10,并裁剪
value = round(float(value_parts[0]) * 10)
value = max(0, min(10000, value))
params[LottieTensor.Index.DashOffset.O] = value
elif cmd_idx == LottieTensor.CMD_DASHES_END:
# End of dashes container
pass
elif cmd_idx == LottieTensor.CMD_SIZE_END:
# End of dashes container
pass
elif cmd_idx == LottieTensor.CMD_RECT_SIZE:
# 检查是否是动画
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
current_context = "size"
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.Transform.X] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.Transform.Y] = round(float(value_parts[1]))
elif cmd_idx == LottieTensor.CMD_ELLIPSE_SIZE:
# 检查是否是动画
if attrs.get("animated", "").lower() == "true":
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
current_context = "size" # 设置上下文用于关键帧
else:
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# 解析静态值
value_parts = []
for part in attrs_str.split():
if '=' not in part:
try:
round(float(part))
value_parts.append(part)
except ValueError:
pass
if len(value_parts) >= 1:
params[LottieTensor.Index.Transform.X] = round(float(value_parts[0]))
if len(value_parts) >= 2:
params[LottieTensor.Index.Transform.Y] = round(float(value_parts[1]))
params_list.append(params)
# Convert to tensors
commands_tensor = torch.tensor(commands).long()
params_tensor = torch.tensor(params_list).float()
lottie_tensor = LottieTensor(commands_tensor, params_tensor)
#lottie_tensor.string_params = string_params
return lottie_tensor
def from_sequence_v2(sequence_str)-> 'LottieTensor':
"""
Parse a sequence string back into a Lottie animation dictionary.
Properly handles nested group structures using a stack.
"""
lines = sequence_str.strip().split('\n')
animation = {
"v": "5.5.2",
"fr": 30,
"ip": 0,
"op": 60,
"w": 512,
"h": 512,
"nm": "Animation",
"ddd": 0,
"assets": [],
"layers": [],
"markers": [],
"props": {},
"fonts": None,
"chars": None
}
# Stack for tracking nested structures
# Each entry is (type, object, items_list)
stack = []
current_layer = None
current_transform = None
current_path = None
current_path_points = []
current_keyframes = []
current_context = None # 'position', 'scale', 'rotation', 'opacity', 'anchor'
def parse_attrs(attr_str):
"""Parse attribute string into dict"""
attrs = {}
if not attr_str:
return attrs
import re
# Handle quoted values
pattern = r'(\w+)=(?:"([^"]*)"|(\S+))'
for match in re.finditer(pattern, attr_str):
key = match.group(1)
value = match.group(2) if match.group(2) is not None else match.group(3)
attrs[key] = value
# Handle space-separated values without keys
parts = attr_str.split()
positional = []
for part in parts:
if '=' not in part:
try:
positional.append(float(part))
except ValueError:
pass
if positional:
attrs['_positional'] = positional
return attrs
def parse_command(line):
"""Parse a command line into (command, attrs_str)"""
line = line.strip()
if not line.startswith('(') or not line.endswith(')'):
return None, ""
content = line[1:-1].strip()
# Handle end tags
if content.startswith('/'):
return content, ""
# Handle quoted commands like "TransformShape"
if content.startswith('"'):
end_quote = content.find('"', 1)
if end_quote > 0:
cmd = content[:end_quote+1]
attrs = content[end_quote+1:].strip()
return cmd, attrs
# Regular command
parts = content.split(' ', 1)
cmd = parts[0]
attrs = parts[1] if len(parts) > 1 else ""
return cmd, attrs
def get_current_items_container():
"""Get the current container for adding items"""
if stack:
return stack[-1][2] # items list
elif current_layer:
return current_layer.get('shapes', [])
return None
def add_to_current_container(item):
"""Add item to current container"""
container = get_current_items_container()
if container is not None:
container.append(item)
for line in lines:
cmd, attrs_str = parse_command(line)
if cmd is None:
continue
attrs = parse_attrs(attrs_str)
# ========== Animation ==========
if cmd == 'animation':
animation['v'] = attrs.get('v', '5.5.2').strip('"')
animation['fr'] = float(attrs.get('fr', 30))
animation['ip'] = float(attrs.get('ip', 0))
animation['op'] = float(attrs.get('op', 60))
animation['w'] = float(attrs.get('w', 512))
animation['h'] = float(attrs.get('h', 512))
animation['ddd'] = int(attrs.get('ddd', 0))
# ========== Layer ==========
elif cmd == 'layer':
current_layer = {
'ddd': int(attrs.get('ddd', 0)),
'ind': int(float(attrs.get('index', 0))),
'ty': 4, # Shape layer
'nm': 'Layer',
'sr': 1,
'ks': {},
'ao': int(attrs.get('ao', 0)),
'shapes': [],
'ip': float(attrs.get('in_point', 0)),
'op': float(attrs.get('out_point', 60)),
'st': float(attrs.get('start_time', 0)),
'bm': 0
}
animation['layers'].append(current_layer)
stack.clear() # New layer, clear stack
elif cmd == '/layer':
current_layer = None
stack.clear()
# ========== Transform ==========
elif cmd == 'transform':
current_transform = {
'o': {'a': 0, 'k': 100, 'ix': 11},
'r': {'a': 0, 'k': 0, 'ix': 10},
'p': {'a': 0, 'k': [0, 0, 0], 'ix': 2},
'a': {'a': 0, 'k': [0, 0, 0], 'ix': 1},
's': {'a': 0, 'k': [100, 100, 100], 'ix': 6}
}
elif cmd == '/transform':
if current_layer and current_transform:
current_layer['ks'] = current_transform
current_transform = None
current_context = None
# ========== Transform properties ==========
elif cmd == 'position':
current_context = 'position'
current_keyframes = []
if 'animated' in attrs and attrs.get('animated', '').lower() == 'true':
if current_transform:
current_transform['p'] = {'a': 1, 'k': [], 'ix': 2}
else:
pos = attrs.get('_positional', [0, 0])
if current_transform:
current_transform['p'] = {'a': 0, 'k': pos + [0] if len(pos) == 2 else pos, 'ix': 2}
elif cmd == '/position':
if current_transform and current_keyframes:
current_transform['p'] = {'a': 1, 'k': current_keyframes, 'ix': 2}
current_context = None
current_keyframes = []
elif cmd == 'scale':
current_context = 'scale'
current_keyframes = []
if 'animated' in attrs and attrs.get('animated', '').lower() == 'true':
if current_transform:
current_transform['s'] = {'a': 1, 'k': [], 'ix': 6}
else:
scale = attrs.get('_positional', [100, 100, 100])
if current_transform:
current_transform['s'] = {'a': 0, 'k': scale, 'ix': 6}
elif cmd == '/scale':
if current_transform and current_keyframes:
current_transform['s'] = {'a': 1, 'k': current_keyframes, 'ix': 6}
current_context = None
current_keyframes = []
elif cmd == 'rotation':
current_context = 'rotation'
current_keyframes = []
if 'animated' in attrs and attrs.get('animated', '').lower() == 'true':
if current_transform:
current_transform['r'] = {'a': 1, 'k': [], 'ix': 10}
else:
rot = attrs.get('_positional', [0])
if current_transform:
current_transform['r'] = {'a': 0, 'k': rot[0] if rot else 0, 'ix': 10}
elif cmd == 'opacity':
current_context = 'opacity'
current_keyframes = []
if 'animated' in attrs and attrs.get('animated', '').lower() == 'true':
if current_transform:
current_transform['o'] = {'a': 1, 'k': [], 'ix': 11}
else:
op = attrs.get('_positional', [100])
if current_transform:
current_transform['o'] = {'a': 0, 'k': op[0] if op else 100, 'ix': 11}
elif cmd == '/opacity':
if current_transform and current_keyframes:
current_transform['o'] = {'a': 1, 'k': current_keyframes, 'ix': 11}
current_context = None
current_keyframes = []
elif cmd == 'anchor':
pos = attrs.get('_positional', [0, 0])
if current_transform:
current_transform['a'] = {'a': 0, 'k': pos + [0] if len(pos) == 2 else pos, 'ix': 1}
# ========== Keyframes ==========
elif cmd == 'keyframe':
t = float(attrs.get('t', 0))
# Parse s value
s_str = attrs.get('s', '0')
if s_str.startswith('"') and s_str.endswith('"'):
s_str = s_str[1:-1]
s_parts = s_str.split()
s_val = [float(x) for x in s_parts] if s_parts else [0]
keyframe = {'t': t, 's': s_val}
# Parse easing
i_x = float(attrs.get('i_x', 0))
i_y = float(attrs.get('i_y', 0))
o_x = float(attrs.get('o_x', 0))
o_y = float(attrs.get('o_y', 0))
if i_x != 0 or i_y != 0 or o_x != 0 or o_y != 0:
keyframe['i'] = {'x': [i_x], 'y': [i_y]}
keyframe['o'] = {'x': [o_x], 'y': [o_y]}
# Parse to/ti
if 'to' in attrs:
to_str = attrs['to'].strip('"[]')
to_parts = [float(x.strip()) for x in to_str.split(',') if x.strip()]
if to_parts:
keyframe['to'] = to_parts
if 'ti' in attrs:
ti_str = attrs['ti'].strip('"[]')
ti_parts = [float(x.strip()) for x in ti_str.split(',') if x.strip()]
if ti_parts:
keyframe['ti'] = ti_parts
current_keyframes.append(keyframe)
# ========== Group ==========
elif cmd == 'group':
group = {
'ty': 'gr',
'nm': 'Group',
'np': int(attrs.get('np', 0)),
'cix': int(attrs.get('cix', 2)),
'bm': int(attrs.get('bm', 0)),
'ix': int(attrs.get('ix', 1)),
'mn': 'ADBE Vector Group',
'hd': attrs.get('hd', 'false').lower() == 'true',
'it': [] # Items go here
}
# Add to current container
add_to_current_container(group)
# Push onto stack
stack.append(('group', group, group['it']))
elif cmd == '/group':
if stack and stack[-1][0] == 'group':
stack.pop()
# ========== Path ==========
elif cmd == 'path':
current_path = {
'ty': 'sh',
'nm': 'Path',
'mn': 'ADBE Vector Shape - Group',
'hd': attrs.get('hd', 'false').lower() == 'true',
'ix': int(attrs.get('ix', 1)),
'ind': int(attrs.get('ind', 0)),
'ks': {
'a': 0,
'k': {
'c': attrs.get('closed', 'true').lower() == 'true',
'v': [],
'i': [],
'o': []
},
'ix': int(attrs.get('ks_ix', 2))
}
}
current_path_points = []
elif cmd == '/path':
if current_path:
# Build bezier from points
bezier = current_path['ks']['k']
for pt in current_path_points:
bezier['v'].append([pt['x'], pt['y']])
bezier['i'].append([pt['in_x'], pt['in_y']])
bezier['o'].append([pt['out_x'], pt['out_y']])
add_to_current_container(current_path)
current_path = None
current_path_points = []
elif cmd == 'point':
pt = {
'x': float(attrs.get('x', 0)),
'y': float(attrs.get('y', 0)),
'in_x': float(attrs.get('in_x', 0)),
'in_y': float(attrs.get('in_y', 0)),
'out_x': float(attrs.get('out_x', 0)),
'out_y': float(attrs.get('out_y', 0))
}
current_path_points.append(pt)
# ========== Fill ==========
elif cmd == 'fill':
fill = {
'ty': 'fl',
'nm': 'Fill',
'mn': 'ADBE Vector Graphic - Fill',
'hd': False,
'c': {
'a': 0,
'k': [
float(attrs.get('r', 0.5)),
float(attrs.get('g', 0.5)),
float(attrs.get('b', 0.5)),
1
],
'ix': int(attrs.get('c_ix', 4))
},
'o': {
'a': 0,
'k': float(attrs.get('opacity', 100)),
'ix': int(attrs.get('o_ix', 5))
},
'r': int(attrs.get('fill_rule', 1)),
'bm': int(attrs.get('bm', 0))
}
add_to_current_container(fill)
# ========== Stroke ==========
elif cmd == 'stroke':
stroke = {
'ty': 'st',
'nm': 'Stroke',
'mn': 'ADBE Vector Graphic - Stroke',
'hd': False,
'c': {
'a': 0,
'k': [
float(attrs.get('r', 0)),
float(attrs.get('g', 0)),
float(attrs.get('b', 0)),
1
],
'ix': int(attrs.get('c_ix', 3))
},
'o': {
'a': 0,
'k': 100,
'ix': 4
},
'w': {
'a': 0,
'k': float(attrs.get('width', 2)),
'ix': 5
},
'lc': int(attrs.get('lc', 2)),
'lj': int(attrs.get('lj', 2)),
'ml': float(attrs.get('ml', 4)),
'bm': int(attrs.get('bm', 0))
}
add_to_current_container(stroke)
# ========== TransformShape (group transform) ==========
elif cmd == '"TransformShape"':
# Parse position
pos_str = attrs.get('position', '0 0').strip('"')
pos_parts = pos_str.split()
pos = [float(x) for x in pos_parts] if pos_parts else [0, 0]
# Parse scale
scale_str = attrs.get('scale', '100 100').strip('"')
scale_parts = scale_str.split()
scale = [float(x) for x in scale_parts] if scale_parts else [100, 100]
# Parse rotation
rot_str = attrs.get('rotation', '0').strip('"')
rot = float(rot_str)
# Parse opacity
op_str = attrs.get('opacity', '100').strip('"')
opacity = float(op_str)
# Parse anchor
anchor_str = attrs.get('anchor', '0 0').strip('"')
anchor_parts = anchor_str.split()
anchor = [float(x) for x in anchor_parts] if anchor_parts else [0, 0]
transform = {
'ty': 'tr',
'p': {'a': 0, 'k': pos, 'ix': 2},
'a': {'a': 0, 'k': anchor, 'ix': 1},
's': {'a': 0, 'k': scale, 'ix': 3},
'r': {'a': 0, 'k': rot, 'ix': 6},
'o': {'a': 0, 'k': opacity, 'ix': 7},
'sk': {'a': 0, 'k': 0, 'ix': 4},
'sa': {'a': 0, 'k': 0, 'ix': 5},
'nm': 'Transform'
}
# Parse skew if present
if 'skew' in attrs:
skew_str = attrs.get('skew', '0').strip('"')
transform['sk']['k'] = float(skew_str)
if 'skew_axis' in attrs:
sa_str = attrs.get('skew_axis', '0').strip('"')
transform['sa']['k'] = float(sa_str)
add_to_current_container(transform)
# ========== Rectangle ==========
elif cmd == 'rect':
rect = {
'ty': 'rc',
'nm': 'Rectangle',
'mn': 'ADBE Vector Shape - Rect',
'hd': attrs.get('hd', 'false').lower() == 'true',
'd': int(attrs.get('d', 1)),
'p': {'a': 0, 'k': [0, 0], 'ix': 3},
's': {'a': 0, 'k': [100, 100], 'ix': 2},
'r': {'a': 0, 'k': 0, 'ix': 4}
}
add_to_current_container(rect)
elif cmd == '/rect':
pass
# ========== Ellipse ==========
elif cmd == 'ellipse':
ellipse = {
'ty': 'el',
'nm': 'Ellipse',
'mn': 'ADBE Vector Shape - Ellipse',
'hd': False,
'd': 1,
'p': {'a': 0, 'k': [0, 0], 'ix': 3},
's': {'a': 0, 'k': [100, 100], 'ix': 2}
}
add_to_current_container(ellipse)
elif cmd == '/ellipse':
pass
return animation
@staticmethod
def _parse_attributes(attrs_str: str) -> Dict[str, str]:
"""Parse attribute string to dictionary - no change needed as it returns strings"""
attrs = {}
# First, handle special attributes with quotes that might contain special characters
# Handle ch attribute specially (for char command)
ch_match = re.search(r'ch="([^"]*)"', attrs_str)
if ch_match:
attrs['ch'] = ch_match.group(1)
# Remove the ch attribute from the string to avoid re-parsing
attrs_str = attrs_str[:ch_match.start()] + attrs_str[ch_match.end():]
# Handle name attribute specially if it contains quotes
name_match = re.search(r'name="([^"]*)"', attrs_str)
if name_match:
attrs['name'] = name_match.group(1)
# Remove the name attribute from the string to avoid re-parsing
attrs_str = attrs_str[:name_match.start()] + attrs_str[name_match.end():]
else:
# Try without quotes
name_match = re.search(r'name=([^\s]+)', attrs_str)
if name_match:
attrs['name'] = name_match.group(1)
attrs_str = attrs_str[:name_match.start()] + attrs_str[name_match.end():]
# Parse remaining attributes
# Pattern for key=value or key="value"
pattern = r'([^\s=]+)=(?:"([^"]*)"|([^\s]*))'
for match in re.finditer(pattern, attrs_str):
key, quoted_val, unquoted_val = match.groups()
if key not in ['name', 'ch']: # Skip if we already handled these
attrs[key] = quoted_val if quoted_val is not None else unquoted_val
return attrs
@staticmethod
def _extract_array_values(array_str: str, max_values: int) -> List[int]:
"""Extract values from array string and return as int list"""
values = [0] * max_values
if array_str:
# Handle quoted array
if array_str.startswith('"') and array_str.endswith('"'):
array_str = array_str[1:-1]
# Handle bracketed array
if array_str.startswith("[") and array_str.endswith("]"):
try:
array_str = array_str.strip('[]')
parts = array_str.split(',')
for i, part in enumerate(parts):
if i >= max_values:
break
values[i] = round(float(part.strip()))
except ValueError:
pass
# Handle space-separated values
else:
parts = array_str.split()
for i, part in enumerate(parts):
if i >= max_values:
break
try:
values[i] = round(float(part))
except ValueError:
pass
return values
@staticmethod
def _format_value(value, preserve_int=True):
"""Format value as integer with proper rounding"""
val = float(value)
# Handle special case for very small values
if abs(val) < 1e-10:
return 0
# Always round to integer
return val
def to_sequence(self) -> str:
"""Convert LottieTensor to sequence string"""
lines = []
current_context = None
string_params = getattr(self, 'string_params', {})
for i in range(self.seq_len.item()):
cmd_idx = int(self.commands[i].item())
# Skip padding and special tokens
if cmd_idx in [LottieTensor.CMD_PAD, LottieTensor.CMD_EOS, LottieTensor.CMD_SOS]:
continue
cmd = LottieTensor.COMMANDS[cmd_idx]
if not cmd: # Skip empty command entries
continue
cmd_key = f"{i}"
# Handle end tags
if cmd.startswith('/'):
lines.append(f"({cmd})")
# Reset context
if cmd in ["/position", "/scale", "/opacity", "/rotation", "/keyframe", "/anchor", "/path", "/width_animated",
"/range_start", "/range_end", "/range_offset", "/scale_animators", "/rotation_animators",
"/position_x", "/position_y", "/position_z", "/tm", "/start", "/end", "/offset", "/color_animated", "/size", "/rounded"]:
current_context = None
continue
# Update context
if cmd in ["position", "scale", "opacity", "rotation", "anchor"]:
current_context = cmd
elif cmd == "size":
# Check if size is animated
params = self.params[i].tolist()
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
current_context = "size"
elif cmd in ["ellipse_size", "rect_size"]:
# Check if size is animated
params = self.params[i].tolist()
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
current_context = "size"
elif cmd in ["position_x", "position_y", "position_z"]:
# Check if animated
params = self.params[i].tolist()
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
current_context = cmd
elif cmd == "path":
# Check if path is animated (would be stored in string_params)
if f"{cmd_key}_animated" in string_params:
current_context = "path"
elif cmd == "width_keyframe":
current_context = "width"
elif cmd == "start":
# Check if animated
params = self.params[i].tolist()
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
current_context = "trim_start"
elif cmd == "end":
# Check if animated
params = self.params[i].tolist()
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
current_context = "trim_end"
elif cmd == "offset":
# Check if animated
params = self.params[i].tolist()
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
current_context = "trim_offset"
elif cmd == "mask_x":
# Check if animated
params = self.params[i].tolist()
if params[LottieTensor.Index.MaskX.A] > 0.5:
current_context = "mask_x"
elif cmd == "scale_animators":
params = self.params[i].tolist()
if params[LottieTensor.Index.ScaleAnimators.A] > 0.5:
current_context = "scale_animators"
elif cmd == "rotation_animators":
params = self.params[i].tolist()
if params[LottieTensor.Index.RotationAnimators.A] > 0.5:
current_context = "rotation_animators"
elif cmd == "opacity_animators":
params = self.params[i].tolist()
if params[LottieTensor.Index.OpacityAnimators.A] > 0.5:
current_context = "opacity_animators"
elif cmd == "position_animators":
params = self.params[i].tolist()
if params[LottieTensor.Index.PositionAnimators.A] > 0.5:
current_context = "position_animators"
elif cmd == "tracking_animators":
params = self.params[i].tolist()
if params[LottieTensor.Index.TrackingAnimators.A] > 0.5:
current_context = "tracking_animators"
elif cmd == "rect_rounded":
# Check if animated
params = self.params[i].tolist()
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
current_context = "rect_rounded"
elif cmd in ["ellipse_size", "rect_size"]:
# Check if ellipse/rect size is animated
params = self.params[i].tolist()
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
current_context = "size"
# Extract parameters
params = self.params[i].tolist()
# Format line based on command type
if cmd_idx == LottieTensor.CMD_ANIMATION:
# Use stored string values if available
#v = string_params.get(f"{cmd_key}_v", "5.12.1")
v = "5.12.1"
#nm = string_params.get(f"{cmd_key}_nm", "Comp 1")
#markers = string_params.get(f"{cmd_key}_markers", "[]")
#props = string_params.get(f"{cmd_key}_props", "{}")
fr = LottieTensor._format_value(params[LottieTensor.Index.Animation.FR])
ip = LottieTensor._format_value(params[LottieTensor.Index.Animation.IP])
op = LottieTensor._format_value(params[LottieTensor.Index.Animation.OP])
w = LottieTensor._format_value(params[LottieTensor.Index.Animation.W])
h = LottieTensor._format_value(params[LottieTensor.Index.Animation.H])
ddd = int(params[LottieTensor.Index.Animation.DDD])
lines.append(f'({cmd} v="{v}" fr={fr} ip={ip} op={op} w={w} h={h} ddd={ddd})')
elif cmd_idx in [LottieTensor.CMD_FONTS, LottieTensor.CMD_FONTS_END, LottieTensor.CMD_CHARS,
LottieTensor.CMD_CHARS_END, LottieTensor.CMD_CHAR_SHAPES,
LottieTensor.CMD_CHAR_SHAPES_END, LottieTensor.CMD_TEXT_KEYFRAMES,
LottieTensor.CMD_TEXT_KEYFRAMES_END, LottieTensor.CMD_TEXT_DATA,
LottieTensor.CMD_TEXT_DATA_END, LottieTensor.CMD_OPACITY_ANIMATED_END,
LottieTensor.CMD_END_END, LottieTensor.CMD_START_END,
LottieTensor.CMD_OFFSET_END, LottieTensor.CMD_OPACITY_ANIMATORS_END]:
lines.append(f"({cmd})")
continue
elif cmd_idx in [LottieTensor.CMD_POSITION_X, LottieTensor.CMD_POSITION_Y, LottieTensor.CMD_POSITION_Z]:
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f"({cmd} animated=true)")
current_context = cmd # Set context
else:
val = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
lines.append(f"({cmd} {val})")
# line based on command type
# Keep all existing formatting logic but update text_keyframe
elif cmd_idx == LottieTensor.CMD_TEXT_KEYFRAME:
# Initialize tokenizer if not already done
if LottieTensor.tokenizer is None:
LottieTensor.init_tokenizer()
t = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.T])
# Retrieve all stored attributes
# Retrieve numeric values
font_size = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.FONT_SIZE])
ca = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.CA])
justify = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.JUSTIFY])
tracking = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.TRACKING])
line_height = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.LINE_HEIGHT])
letter_spacing = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.LETTER_SPACING])
# Retrieve fill_color from numeric params
fill_r = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.FILL_COLOR_R]/255)
fill_g = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.FILL_COLOR_G]/255)
fill_b = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.FILL_COLOR_B]/255)
fill_color = f"[{fill_r},{fill_g},{fill_b}]"
# Retrieve string values
#font_family = string_params.get(f"{cmd_key}_font_family", "")
#text = string_params.get(f"{cmd_key}_text", "")
# Decode font_family from tokens
font_family = ""
font_family_count = int(params[LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKEN_COUNT]) if params[LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKEN_COUNT] > -2000 else 0
if font_family_count > 0:
font_family_tokens = []
for i in range(min(font_family_count, 10)):
token_val = params[LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKENS_START + i]
if token_val > -2000:
font_family_tokens.append(int(token_val))
if font_family_tokens:
try:
font_family = LottieTensor.tokenizer.decode(font_family_tokens)
except:
font_family = ""
# Decode text from tokens
text = ""
text_count = int(params[LottieTensor.Index.TextKeyframe.TEXT_TOKEN_COUNT]) if params[LottieTensor.Index.TextKeyframe.TEXT_TOKEN_COUNT] > -2000 else 0
if text_count > 0:
text_tokens = []
for i in range(min(text_count, 15)):
token_val = params[LottieTensor.Index.TextKeyframe.TEXT_TOKENS_START + i]
if token_val > -2000:
text_tokens.append(int(token_val))
if text_tokens:
try:
text = LottieTensor.tokenizer.decode(text_tokens)
except:
text = ""
# Build the output line
line = f'({cmd} t={t} font_size={font_size} font_family="{font_family}" text="{text}" ca={ca} justify={justify} tracking={tracking} line_height={line_height} letter_spacing={letter_spacing} fill_color={fill_color}'
# Add stroke_color if present
if params[LottieTensor.Index.TextKeyframe.HAS_STROKE_COLOR] > 0.5:
stroke_r = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.STROKE_COLOR_R]/255)
stroke_g = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.STROKE_COLOR_G]/255)
stroke_b = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.STROKE_COLOR_B]/255)
stroke_color = f"[{stroke_r},{stroke_g},{stroke_b}]"
line += f' stroke_color={stroke_color}'
# Add stroke_width if present and not zero
stroke_width = params[LottieTensor.Index.TextKeyframe.STROKE_WIDTH] if params[LottieTensor.Index.TextKeyframe.STROKE_WIDTH] > -2000 else 0
if abs(stroke_width) > 1e-6:
line += f' stroke_width={LottieTensor._format_value(stroke_width)}'
# Add offset if true
if params[LottieTensor.Index.TextKeyframe.OFFSET] > 0.5:
line += ' offset=true'
# Add wrap_position if present (新增)
wrap_pos_x = params[LottieTensor.Index.TextKeyframe.WRAP_POSITION_X]
wrap_pos_y = params[LottieTensor.Index.TextKeyframe.WRAP_POSITION_Y]
if wrap_pos_x > -2000 and wrap_pos_y > -2000:
line += f' wrap_position=[{LottieTensor._format_value(wrap_pos_x)},{LottieTensor._format_value(wrap_pos_y)}]'
# Add wrap_size if present (新增)
wrap_size_x = params[LottieTensor.Index.TextKeyframe.WRAP_SIZE_X]
wrap_size_y = params[LottieTensor.Index.TextKeyframe.WRAP_SIZE_Y]
if wrap_size_x > -2000 and wrap_size_y > -2000:
line += f' wrap_size=[{LottieTensor._format_value(wrap_size_x)},{LottieTensor._format_value(wrap_size_y)}]'
line += ')'
lines.append(line)
elif cmd_idx == LottieTensor.CMD_STAR:
#name = string_params.get(f"{cmd_key}_name", "None")
d = int(params[LottieTensor.Index.Star.D]) if params[LottieTensor.Index.Star.D] > -2000 else 1
sy = int(params[LottieTensor.Index.Star.SY]) if params[LottieTensor.Index.Star.SY] > -2000 else 1
lines.append(f'({cmd} d={d} sy={sy})')
elif cmd_idx == LottieTensor.CMD_INNER_RADIUS:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_OUTER_RADIUS:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_INNER_ROUNDNESS:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_OUTER_ROUNDNESS:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_POINTS_STAR: # 这会输出 points_star
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'(points_star {val})') # 强制输出为 points_star
elif cmd_idx == LottieTensor.CMD_STAR_ROTATION:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_MORE_OPTIONS:
# Reconstruct more_options line
g = int(params[LottieTensor.Index.MoreOptions.G]) if params[LottieTensor.Index.MoreOptions.G] > -2000 else 1
alignment_a = int(params[LottieTensor.Index.MoreOptions.ALIGNMENT_A]) if params[LottieTensor.Index.MoreOptions.ALIGNMENT_A] > -2000 else 0
alignment_k1 = int(params[LottieTensor.Index.MoreOptions.ALIGNMENT_K1]) if params[LottieTensor.Index.MoreOptions.ALIGNMENT_K1] > -2000 else 0
alignment_k2 = int(params[LottieTensor.Index.MoreOptions.ALIGNMENT_K2]) if params[LottieTensor.Index.MoreOptions.ALIGNMENT_K2] > -2000 else 0
alignment_ix = int(params[LottieTensor.Index.MoreOptions.ALIGNMENT_IX]) if params[LottieTensor.Index.MoreOptions.ALIGNMENT_IX] > -2000 else 2
lines.append(f'({cmd} g {g} alignment a={alignment_a} alignment_k {alignment_k1} {alignment_k2} alignment_ix {alignment_ix})')
elif cmd_idx == LottieTensor.CMD_LAYER:
# Use stored layer name if available
#name = string_params.get(f"{cmd_key}_name", "Layer")
index = LottieTensor._format_value(params[LottieTensor.Index.Layer.INDEX])
in_point = LottieTensor._format_value(params[LottieTensor.Index.Layer.IN_POINT])
out_point = LottieTensor._format_value(params[LottieTensor.Index.Layer.OUT_POINT])
start_time = LottieTensor._format_value(params[LottieTensor.Index.Layer.START_TIME])
# 开始构建输出行
line = f'({cmd} index={index} in_point={in_point} out_point={out_point} start_time={start_time}'
# 只输出非默认/非填充值的可选参数
if params[LottieTensor.Index.Layer.DDD] > -2000:
ddd = int(params[LottieTensor.Index.Layer.DDD])
line += f' ddd={ddd}'
#if params[LottieTensor.Index.Layer.HD] > -2000:
# hd = "true" if params[LottieTensor.Index.Layer.HD] > 0.5 else "false"
# line += f' hd={hd}'
if params[LottieTensor.Index.Layer.HD] > -2000 and params[LottieTensor.Index.Layer.HD] > 0.5:
line += f' hd=true'
if params[LottieTensor.Index.Layer.CP] > -2000:
cp = "true" if params[LottieTensor.Index.Layer.CP] > 0.5 else "false"
line += f' cp={cp}'
if params[LottieTensor.Index.Layer.CT] > -2000:
ct = int(params[LottieTensor.Index.Layer.CT])
line += f' ct={ct}'
if params[LottieTensor.Index.Layer.HAS_MASK] > -2000:
hasMask = "true" if params[LottieTensor.Index.Layer.HAS_MASK] > 0.5 else "false"
line += f' hasMask={hasMask}'
# masksProperties 总是作为字符串存储
masksProperties = string_params.get(f"{cmd_key}_masksProperties", "")
if masksProperties:
line += f' masksProperties={masksProperties}'
if params[LottieTensor.Index.Layer.AO] > -2000:
ao = int(params[LottieTensor.Index.Layer.AO])
line += f' ao={ao}'
if params[LottieTensor.Index.Layer.TT] > -2000:
tt = int(params[LottieTensor.Index.Layer.TT])
line += f' tt={tt}'
if params[LottieTensor.Index.Layer.TP] > -2000:
tp = int(params[LottieTensor.Index.Layer.TP])
line += f' tp={tp}'
if params[LottieTensor.Index.Layer.TD] > -2000:
td = int(params[LottieTensor.Index.Layer.TD])
line += f' td={td}'
line += ')'
lines.append(line)
elif cmd_idx == LottieTensor.CMD_NULL_LAYER:
# Use stored layer name if available
#name = string_params.get(f"{cmd_key}_name", "null_layer")
index = LottieTensor._format_value(params[LottieTensor.Index.NullLayer.INDEX])
in_point = LottieTensor._format_value(params[LottieTensor.Index.NullLayer.IN_POINT])
out_point = LottieTensor._format_value(params[LottieTensor.Index.NullLayer.OUT_POINT])
start_time = LottieTensor._format_value(params[LottieTensor.Index.NullLayer.START_TIME])
line = f'({cmd} index={index} in_point={in_point} out_point={out_point} start_time={start_time}'
#lines.append(f'({cmd} index={index} name="{name}" in_point={in_point} out_point={out_point} start_time={start_time} ct={ct})')
if params[LottieTensor.Index.PrecompLayer.HD] > -2000 and params[LottieTensor.Index.PrecompLayer.HD] > 0.5:
line += f' hd=true'
if params[LottieTensor.Index.PrecompLayer.CP] > -2000:
cp = "true" if params[LottieTensor.Index.PrecompLayer.CP] > 0.5 else "false"
line += f' cp={cp}'
if params[LottieTensor.Index.PrecompLayer.HAS_MASK] > -2000:
hasMask = "true" if params[LottieTensor.Index.PrecompLayer.HAS_MASK] > 0.5 else "false"
line += f' hasMask={hasMask}'
if params[LottieTensor.Index.PrecompLayer.AO] > -2000:
ao = int(params[LottieTensor.Index.PrecompLayer.AO])
line += f' ao={ao}'
if params[LottieTensor.Index.PrecompLayer.TT] > -2000:
tt = int(params[LottieTensor.Index.PrecompLayer.TT])
line += f' tt={tt}'
if params[LottieTensor.Index.PrecompLayer.TP] > -2000:
tp = int(params[LottieTensor.Index.PrecompLayer.TP])
line += f' tp={tp}'
if params[LottieTensor.Index.PrecompLayer.TD] > -2000:
td = int(params[LottieTensor.Index.PrecompLayer.TD])
line += f' td={td}'
line += ')'
lines.append(line)
elif cmd_idx == LottieTensor.CMD_PRECOMP_LAYER:
# Use stored layer name if available
#name = string_params.get(f"{cmd_key}_name", "precomp_layer")
index = LottieTensor._format_value(params[LottieTensor.Index.PrecompLayer.INDEX])
in_point = LottieTensor._format_value(params[LottieTensor.Index.PrecompLayer.IN_POINT])
out_point = LottieTensor._format_value(params[LottieTensor.Index.PrecompLayer.OUT_POINT])
start_time = LottieTensor._format_value(params[LottieTensor.Index.PrecompLayer.START_TIME])
# Output w and h with full precision
#w = params[LottieTensor.Index.PrecompLayer.W]
#h = params[LottieTensor.Index.PrecompLayer.H]
# Format w and h preserving their full precision
# Check if the value is very close to an integer
#if abs(w - round(w)) < 1e-10:
# w_str = str(int(round(w)))
#else:
# Keep full precision for non-integer values
# w_str = str(w)
#if abs(h - round(h)) < 1e-10:
# h_str = str(int(round(h)))
#else:
# Keep full precision for non-integer values
# h_str = str(h)
line = f'({cmd} index={index} in_point={in_point} out_point={out_point} start_time={start_time}'
if params[LottieTensor.Index.PrecompLayer.H] > -2000:
h = int(params[LottieTensor.Index.PrecompLayer.H])
line += f' h={h}'
if params[LottieTensor.Index.PrecompLayer.W] > -2000:
w = int(params[LottieTensor.Index.PrecompLayer.W])
line += f' w={w}'
if params[LottieTensor.Index.PrecompLayer.DDD] > -2000:
ddd = int(params[LottieTensor.Index.PrecompLayer.DDD])
line += f' ddd={ddd}'
if params[LottieTensor.Index.PrecompLayer.HD] > -2000 and params[LottieTensor.Index.PrecompLayer.HD] > 0.5:
line += f' hd=true'
if params[LottieTensor.Index.PrecompLayer.CP] > -2000:
cp = "true" if params[LottieTensor.Index.PrecompLayer.CP] > 0.5 else "false"
line += f' cp={cp}'
if params[LottieTensor.Index.PrecompLayer.CT] > -2000:
ct = int(params[LottieTensor.Index.PrecompLayer.CT])
line += f' ct={ct}'
if params[LottieTensor.Index.PrecompLayer.HAS_MASK] > -2000:
hasMask = "true" if params[LottieTensor.Index.PrecompLayer.HAS_MASK] > 0.5 else "false"
line += f' hasMask={hasMask}'
# masksProperties 总是作为字符串存储
masksProperties = string_params.get(f"{cmd_key}_masksProperties", "")
if masksProperties:
line += f' masksProperties={masksProperties}'
if params[LottieTensor.Index.PrecompLayer.AO] > -2000:
ao = int(params[LottieTensor.Index.PrecompLayer.AO])
line += f' ao={ao}'
if params[LottieTensor.Index.PrecompLayer.TT] > -2000:
tt = int(params[LottieTensor.Index.PrecompLayer.TT])
line += f' tt={tt}'
if params[LottieTensor.Index.PrecompLayer.TP] > -2000:
tp = int(params[LottieTensor.Index.PrecompLayer.TP])
line += f' tp={tp}'
if params[LottieTensor.Index.PrecompLayer.TD] > -2000:
td = int(params[LottieTensor.Index.PrecompLayer.TD])
line += f' td={td}'
line += ')'
lines.append(line)
elif cmd_idx == LottieTensor.CMD_REFERENCE_ID:
# Get tokenizer
tokenizer = LottieTensor.get_tokenizer()
# Decode reference_id from tokens
id_count = int(params[LottieTensor.Index.ReferenceId.ID_TOKEN_COUNT]) if params[LottieTensor.Index.ReferenceId.ID_TOKEN_COUNT] > -2000 else 0
id_tokens = []
for i in range(id_count):
if params[LottieTensor.Index.ReferenceId.ID_TOKEN_0 + i] > -2000:
id_tokens.append(int(params[LottieTensor.Index.ReferenceId.ID_TOKEN_0 + i]))
reference_id = tokenizer.decode(id_tokens, skip_special_tokens=True) if id_tokens else "comp_0"
lines.append(f'({cmd} "{reference_id}")')
elif cmd_idx == LottieTensor.CMD_DIMENSIONS:
# 输出dimensions命令
width = LottieTensor._format_value(params[LottieTensor.Index.Dimensions.WIDTH])
height = LottieTensor._format_value(params[LottieTensor.Index.Dimensions.HEIGHT])
lines.append(f'({cmd} width={width} height={height})')
elif cmd_idx == LottieTensor.CMD_STROKE:
#name = string_params.get(f"{cmd_key}_name", "Stroke")
color_animated = params[LottieTensor.Index.Stroke.COLOR_ANIMATED] > 0.5
line = f'({cmd}'
if color_animated:
line += ' color_animated=true'
else:
# Convert RGB values from 0-255 back to 0-1 range
r = LottieTensor._format_value(params[LottieTensor.Index.Stroke.R] / 255)
g = LottieTensor._format_value(params[LottieTensor.Index.Stroke.G] / 255)
b = LottieTensor._format_value(params[LottieTensor.Index.Stroke.B] / 255)
a = LottieTensor._format_value(params[LottieTensor.Index.Stroke.A] / 255)
line += f' r={r} g={g} b={b} a={a}'
color_dim = int(params[LottieTensor.Index.Stroke.COLOR_DIM]) if params[LottieTensor.Index.Stroke.COLOR_DIM] > -2000 else 4
has_c_a = "True" if params[LottieTensor.Index.Stroke.HAS_C_A] > 0.5 else "False"
has_c_ix = "True" if params[LottieTensor.Index.Stroke.HAS_C_IX] > 0.5 else "False"
c_ix = int(params[LottieTensor.Index.Stroke.C_IX]) if params[LottieTensor.Index.Stroke.C_IX] > -2000 else 3
bm = int(params[LottieTensor.Index.Stroke.BM]) if params[LottieTensor.Index.Stroke.BM] > -2000 else 0
lc = int(params[LottieTensor.Index.Stroke.LC]) if params[LottieTensor.Index.Stroke.LC] > -2000 else 1
lj = int(params[LottieTensor.Index.Stroke.LJ]) if params[LottieTensor.Index.Stroke.LJ] > -2000 else 1
ml = int(params[LottieTensor.Index.Stroke.ML]) if params[LottieTensor.Index.Stroke.ML] > -2000 else 4
line += f' color_dim={color_dim} has_c_a={has_c_a} has_c_ix={has_c_ix}'
if not color_animated:
line += f' c_ix={c_ix}'
line += f' bm={bm} lc={lc} lj={lj} ml={ml}'
# Check if width is animated
width_animated = params[LottieTensor.Index.Stroke.WIDTH_ANIMATED] > 0.5
if width_animated:
line += ' width_animated=true'
current_context = "width"
# IMPORTANT: Close the parenthesis here
line += ')'
lines.append(line)
# Add the separate (width_animated true) command after the stroke
if width_animated:
lines.append('(width_animated true)')
if color_animated:
current_context = "stroke_color"
# Add dashes output in to_sequence method (after line 5800):
elif cmd_idx == LottieTensor.CMD_DASHES:
# Check if we have the complete dashes string stored
dashes_str = string_params.get(f"{cmd_key}_dashes", "")
if dashes_str:
# The stored string already includes quotes if needed, don't add extra quotes
lines.append(f'({cmd} {dashes_str})')
else:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_DASH:
type_map = {0: "d", 1: "g", 2: "o"}
type_val = int(params[LottieTensor.Index.Dash.TYPE]) if params[LottieTensor.Index.Dash.TYPE] > -2000 else 0
dash_type = type_map.get(type_val, "d")
# 除以100恢复原值
length = LottieTensor._format_value(params[LottieTensor.Index.Dash.LENGTH] / 10, preserve_int=False)
v_ix = int(params[LottieTensor.Index.Dash.V_IX]) if params[LottieTensor.Index.Dash.V_IX] > -2000 else 1
lines.append(f'({cmd} type="{dash_type}" length={length} v_ix={v_ix})')
elif cmd_idx == LottieTensor.CMD_DASH_ANIMATED:
# Convert numeric type back to string
type_map = {0: "d", 1: "g", 2: "o"}
type_val = int(params[LottieTensor.Index.DashAnimated.TYPE]) if params[LottieTensor.Index.DashAnimated.TYPE] > -2000 else 2
dash_type = type_map.get(type_val, "o")
v_ix = int(params[LottieTensor.Index.DashAnimated.V_IX]) if params[LottieTensor.Index.DashAnimated.V_IX] > -2000 else 7
# Get name from string_params
name = string_params.get(f"{cmd_key}_name", "")
lines.append(f'({cmd} type="{dash_type}" name="{name}" v_ix={v_ix})')
current_context = "dash_animated"
elif cmd_idx == LottieTensor.CMD_DASH_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.DashKeyframe.T])
# 除以100恢复原值
s = LottieTensor._format_value(params[LottieTensor.Index.DashKeyframe.S] / 10, preserve_int=False)
i_x = params[LottieTensor.Index.DashKeyframe.I_X]/100
i_y = params[LottieTensor.Index.DashKeyframe.I_Y]/100
o_x = params[LottieTensor.Index.DashKeyframe.O_X]/100
o_y = params[LottieTensor.Index.DashKeyframe.O_Y]/100
has_easing = (i_x > -2000 or i_y > -2000 or o_x > -2000 or o_y > -2000)
if has_easing:
i_x_val = LottieTensor._format_value(i_x, preserve_int=False) if i_x > -2000 else 0
i_y_val = LottieTensor._format_value(i_y, preserve_int=False) if i_y > -2000 else 0
o_x_val = LottieTensor._format_value(o_x, preserve_int=False) if o_x > -2000 else 0
o_y_val = LottieTensor._format_value(o_y, preserve_int=False) if o_y > -2000 else 0
lines.append(f'({cmd} t={t} s={s} i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val})')
else:
lines.append(f'({cmd} t={t} s={s})')
elif cmd_idx == LottieTensor.CMD_DASH_ANIMATED_END:
lines.append(f'({cmd})')
current_context = None
elif cmd_idx == LottieTensor.CMD_DASH_OFFSET:
# 除以100恢复原值
o = LottieTensor._format_value(params[LottieTensor.Index.DashOffset.O] / 10, preserve_int=False)
lines.append(f'({cmd} {o})')
elif cmd_idx == LottieTensor.CMD_DASHES_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_DASH_ANIMATED_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_SIZE_END:
lines.append(f'({cmd})')
# 7. Add output for color_keyframe:
elif cmd_idx == LottieTensor.CMD_COLOR_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.T])
r = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.S1]/255)
g = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.S2]/255)
b = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.S3]/255)
a = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E1]/255)
i_x = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_X]/100, preserve_int=False)
i_y = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_Y]/100, preserve_int=False)
o_x = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_X]/100, preserve_int=False)
o_y = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_Y]/100, preserve_int=False)
lines.append(f'({cmd} t={t} r={r} g={g} b={b} a={a} i_x={i_x} i_y={i_y} o_x={o_x} o_y={o_y})')
elif cmd_idx == LottieTensor.CMD_OPACITY_ANIMATED:
lines.append(f'({cmd} true)')
current_context = "opacity_animated"
elif cmd_idx == LottieTensor.CMD_OPACITY_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.T])
keyframe_line = f'({cmd} t={t}'
# Check if s parameter is valid
if params[LottieTensor.Index.Keyframe.S1] > -2000:
s = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.S1])
keyframe_line += f' s={s}'
# Check if easing parameters exist
i_x = params[LottieTensor.Index.Keyframe.I_X]/100
i_y = params[LottieTensor.Index.Keyframe.I_Y]/100
o_x = params[LottieTensor.Index.Keyframe.O_X]/100
o_y = params[LottieTensor.Index.Keyframe.O_Y]/100
if i_x > -2000 or i_y > -2000 or o_x > -2000 or o_y > -2000:
i_x_val = LottieTensor._format_value(i_x, preserve_int=False) if i_x > -2000 else 0
i_y_val = LottieTensor._format_value(i_y, preserve_int=False) if i_y > -2000 else 0
o_x_val = LottieTensor._format_value(o_x, preserve_int=False) if o_x > -2000 else 0
o_y_val = LottieTensor._format_value(o_y, preserve_int=False) if o_y > -2000 else 0
keyframe_line += f' i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val}'
keyframe_line += ')'
lines.append(keyframe_line)
elif cmd_idx == LottieTensor.CMD_WIDTH_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.WidthKeyframe.T])
keyframe_line = f'({cmd} t={t}'
# Check if s parameter is valid - 除以100恢复原值
if params[LottieTensor.Index.WidthKeyframe.S] > -2000:
s = LottieTensor._format_value(params[LottieTensor.Index.WidthKeyframe.S] / 10, preserve_int=False)
keyframe_line += f' s={s}'
# easing参数处理(保持不变,除以100)
i_x = params[LottieTensor.Index.WidthKeyframe.I_X]/100
i_y = params[LottieTensor.Index.WidthKeyframe.I_Y]/100
o_x = params[LottieTensor.Index.WidthKeyframe.O_X]/100
o_y = params[LottieTensor.Index.WidthKeyframe.O_Y]/100
has_easing = (i_x > -2000 or i_y > -2000 or o_x > -2000 or o_y > -2000)
if has_easing:
i_x_val = LottieTensor._format_value(i_x, preserve_int=False) if i_x > -2000 else 0
i_y_val = LottieTensor._format_value(i_y, preserve_int=False) if i_y > -2000 else 0
o_x_val = LottieTensor._format_value(o_x, preserve_int=False) if o_x > -2000 else 0
o_y_val = LottieTensor._format_value(o_y, preserve_int=False) if o_y > -2000 else 0
keyframe_line += f' i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val}'
keyframe_line += ")"
lines.append(keyframe_line)
elif cmd_idx == LottieTensor.CMD_TRANSFORM:
lines.append(f"({cmd})")
elif cmd_idx == LottieTensor.CMD_POSITION:
if cmd == "position" and current_context not in ["position", "scale", "opacity", "rotation", "anchor"]:
# This is a position command for shapes (ellipse, rect, etc.)
x = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE1])
y = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE2])
lines.append(f"({cmd} {x} {y})")
else:
# This is a transform position
if params[LottieTensor.Index.Transform.ANIMATED] == 2.0:
# Separated position (for 3D layers)
lines.append(f"({cmd} separated=true)")
elif params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f"({cmd} animated=true)")
else:
x = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
y = LottieTensor._format_value(params[LottieTensor.Index.Transform.Y])
z = LottieTensor._format_value(params[LottieTensor.Index.Transform.Z])
# Check if Z is meaningful
if params[LottieTensor.Index.Transform.Z] > -2000 and abs(params[LottieTensor.Index.Transform.Z]) > 1e-6:
lines.append(f"({cmd} {x} {y} {z})")
else:
lines.append(f"({cmd} {x} {y})")
elif cmd_idx == LottieTensor.CMD_POSITION_X:
# Output position_x with its value
if params[LottieTensor.Index.SingleValue.VALUE] > -2000:
value = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f"({cmd} {value})")
else:
lines.append(f"({cmd})")
elif cmd_idx == LottieTensor.CMD_POSITION_Y:
# Output position_y with its value
if params[LottieTensor.Index.SingleValue.VALUE] > -2000:
value = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f"({cmd} {value})")
else:
lines.append(f"({cmd})")
elif cmd_idx == LottieTensor.CMD_POSITION_Z:
# Output position_z with its value
if params[LottieTensor.Index.SingleValue.VALUE] > -2000:
value = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f"({cmd} {value})")
else:
lines.append(f"({cmd})")
elif cmd_idx == LottieTensor.CMD_SCALE:
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f"({cmd} animated=true)")
else:
x = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
y = LottieTensor._format_value(params[LottieTensor.Index.Transform.Y])
z = LottieTensor._format_value(params[LottieTensor.Index.Transform.Z])
# Check if Z is meaningful - output Z if it's not padding value
if params[LottieTensor.Index.Transform.Z] > -2000:
lines.append(f"({cmd} {x} {y} {z})")
else:
lines.append(f"({cmd} {x} {y})")
elif cmd_idx == LottieTensor.CMD_ROTATION:
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f"({cmd} animated=true)")
else:
angle = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
lines.append(f"({cmd} {angle})")
elif cmd_idx == LottieTensor.CMD_OPACITY:
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f"({cmd} animated=true)")
else:
val = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
lines.append(f"({cmd} {val})")
elif cmd_idx == LottieTensor.CMD_ANCHOR:
if params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f"({cmd} animated=true)")
else:
x = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
y = LottieTensor._format_value(params[LottieTensor.Index.Transform.Y])
z = LottieTensor._format_value(params[LottieTensor.Index.Transform.Z])
# Check if Z is meaningful
if params[LottieTensor.Index.Transform.Z] > -2000 and abs(params[LottieTensor.Index.Transform.Z]) > 1e-6:
lines.append(f"({cmd} {x} {y} {z})")
else:
lines.append(f"({cmd} {x} {y})")
elif cmd_idx == LottieTensor.CMD_TM:
a = int(params[LottieTensor.Index.Tm.A]) if params[LottieTensor.Index.Tm.A] > -2000 else 1
# Always output a parameter
lines.append(f'({cmd} a={a})')
# Set context based on a value
if a > 0.5:
current_context = "tm" # Set context for keyframes
else:
current_context = "tm_static" # Reset context for static value
# Add value command output
elif cmd_idx == LottieTensor.CMD_VALUE:
val = LottieTensor._format_value(params[LottieTensor.Index.Value.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.T])
keyframe_line = f'({cmd} t={t}'
# Check if this is a hold keyframe (using H_FLAG slot)
is_hold = params[LottieTensor.Index.Keyframe.H_FLAG] > 0.5
# Check if s parameter is valid (not padding)
has_s = params[LottieTensor.Index.Keyframe.S1] > -2000
# Only add s parameter if valid and not in path context
if has_s and current_context != "path":
# Format s parameter based on context
if current_context in ["opacity", "rotation", "position_x", "position_y", "position_z", "tm", "width",
"trim_start", "trim_end", "trim_offset", "mask_x", "rotation_animators", "opacity_animators", "tracking_animators"]:
# For single-value properties
s = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.S1], preserve_int=False)
s_str = f'"{s}"'
else:
# For position, scale, anchor - output only non-padding values
s1 = params[LottieTensor.Index.Keyframe.S1]
s2 = params[LottieTensor.Index.Keyframe.S2]
s3 = params[LottieTensor.Index.Keyframe.S3]
s_parts = []
s_parts.append(str(LottieTensor._format_value(s1, preserve_int=False)))
s_parts.append(str(LottieTensor._format_value(s2, preserve_int=False)))
# Only add s3 if it's not padding value and not zero (for 2D animations)
if s3 > -2000 and abs(s3) > 1e-6:
s_parts.append(str(LottieTensor._format_value(s3, preserve_int=False)))
s_str = f'"{" ".join(s_parts)}"'
keyframe_line += f' s={s_str}'
# Check and output e parameter based on context - MODIFIED TO INCLUDE TRIM CONTEXTS
has_e = params[LottieTensor.Index.Keyframe.E1] > -2000
if has_e: # Output e regardless of hold flag
if current_context in ["trim_start", "trim_end", "trim_offset"]:
# For trim contexts, output single e value as quoted string
e_val = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E1], preserve_int=False)
keyframe_line += f' e="{e_val}"'
elif current_context == "rotation":
# For rotation, output single e value
e_val = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E1], preserve_int=False)
keyframe_line += f' e="[{e_val}]"'
elif current_context == "scale":
# For scale, output three e values
e1 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E1], preserve_int=False) if params[LottieTensor.Index.Keyframe.E1] > -2000 else 0
e2 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E2], preserve_int=False) if params[LottieTensor.Index.Keyframe.E2] > -2000 else 0
e3 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E3], preserve_int=False) if params[LottieTensor.Index.Keyframe.E3] > -2000 else 0
keyframe_line += f' e="[{e1}, {e2}, {e3}]"'
# Add other contexts as needed
# Handle hold keyframe
if is_hold:
keyframe_line += ' h=1'
# ALWAYS check and add easing parameters, regardless of hold flag
# The hold flag just means the value is held, but easing can still be defined
if current_context in ["position", "anchor", "scale_animators", "position_animators", "size"]:
# For multi-dimensional properties, output multi-dimensional easing
# Check if we have multi-dimensional easing values
has_multi_easing = (
params[LottieTensor.Index.Keyframe.I_X2] > -2000 or
params[LottieTensor.Index.Keyframe.I_Y2] > -2000 or
params[LottieTensor.Index.Keyframe.O_X2] > -2000 or
params[LottieTensor.Index.Keyframe.O_Y2] > -2000
)
if has_multi_easing:
# Format multi-dimensional easing - DIVIDE BY 100
i_x_vals = []
i_y_vals = []
o_x_vals = []
o_y_vals = []
# Always include first two dimensions - DIVIDE BY 100
i_x1 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_X] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.I_X] > -2000 else 0
i_x2 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_X2] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.I_X2] > -2000 else i_x1
i_x_vals = [i_x1, i_x2]
i_y1 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_Y] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.I_Y] > -2000 else 0
i_y2 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_Y2] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.I_Y2] > -2000 else i_y1
i_y_vals = [i_y1, i_y2]
o_x1 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_X] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.O_X] > -2000 else 0
o_x2 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_X2] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.O_X2] > -2000 else o_x1
o_x_vals = [o_x1, o_x2]
o_y1 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_Y] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.O_Y] > -2000 else 0
o_y2 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_Y2] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.O_Y2] > -2000 else o_y1
o_y_vals = [o_y1, o_y2]
# Only add third dimension if it exists and is non-zero - DIVIDE BY 100
i_x3 = params[LottieTensor.Index.Keyframe.I_X3]
i_y3 = params[LottieTensor.Index.Keyframe.I_Y3]
o_x3 = params[LottieTensor.Index.Keyframe.O_X3]
o_y3 = params[LottieTensor.Index.Keyframe.O_Y3]
# Check if ANY third dimension value is meaningful (not padding and not zero)
has_third_dim = (
(i_x3 > -2000 and abs(i_x3) > 1e-6) or
(i_y3 > -2000 and abs(i_y3) > 1e-6) or
(o_x3 > -2000 and abs(o_x3) > 1e-6) or
(o_y3 > -2000 and abs(o_y3) > 1e-6)
)
if has_third_dim:
i_x_vals.append(LottieTensor._format_value(i_x3 / 100, preserve_int=False) if i_x3 > -2000 else i_x1)
i_y_vals.append(LottieTensor._format_value(i_y3 / 100, preserve_int=False) if i_y3 > -2000 else i_y1)
o_x_vals.append(LottieTensor._format_value(o_x3 / 100, preserve_int=False) if o_x3 > -2000 else o_x1)
o_y_vals.append(LottieTensor._format_value(o_y3 / 100, preserve_int=False) if o_y3 > -2000 else o_y1)
keyframe_line += f' i_x="{" ".join(str(v) for v in i_x_vals)}" i_y="{" ".join(str(v) for v in i_y_vals)}" o_x="{" ".join(str(v) for v in o_x_vals)}" o_y="{" ".join(str(v) for v in o_y_vals)}"'
elif params[LottieTensor.Index.Keyframe.I_X] > -2000 or params[LottieTensor.Index.Keyframe.I_Y] > -2000 or params[LottieTensor.Index.Keyframe.O_X] > -2000 or params[LottieTensor.Index.Keyframe.O_Y] > -2000:
# Fallback to single values if no multi-dimensional values found - DIVIDE BY 100
i_x_val = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_X] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.I_X] > -2000 else 0
i_y_val = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.I_Y] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.I_Y] > -2000 else 0
o_x_val = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_X] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.O_X] > -2000 else 0
o_y_val = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.O_Y] / 100, preserve_int=False) if params[LottieTensor.Index.Keyframe.O_Y] > -2000 else 0
keyframe_line += f' i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val}'
else:
# For single-dimensional properties, parse single easing values - DIVIDE BY 100
i_x = params[LottieTensor.Index.Keyframe.I_X]
i_y = params[LottieTensor.Index.Keyframe.I_Y]
o_x = params[LottieTensor.Index.Keyframe.O_X]
o_y = params[LottieTensor.Index.Keyframe.O_Y]
if i_x > -2000 or i_y > -2000 or o_x > -2000 or o_y > -2000:
i_x_val = LottieTensor._format_value(i_x / 100, preserve_int=False) if i_x > -2000 else 0
i_y_val = LottieTensor._format_value(i_y / 100, preserve_int=False) if i_y > -2000 else 0
o_x_val = LottieTensor._format_value(o_x / 100, preserve_int=False) if o_x > -2000 else 0
o_y_val = LottieTensor._format_value(o_y / 100, preserve_int=False) if o_y > -2000 else 0
keyframe_line += f' i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val}'
# Add to/ti parameters (they are separate from hold flag)
has_to = any(params[LottieTensor.Index.Keyframe.TO1 + j] > -2000 for j in range(3))
has_ti = any(params[LottieTensor.Index.Keyframe.TI1 + j] > -2000 for j in range(3))
if has_to:
to_values = []
for i in range(3):
val = params[LottieTensor.Index.Keyframe.TO1 + i]
if val > -2000:
to_values.append(LottieTensor._format_value(val, preserve_int=False))
else:
break # Stop at first padding value
# Only output non-zero values, but always include at least 2 dimensions if any exist
while len(to_values) > 2 and abs(to_values[-1]) < 1e-10:
to_values.pop() # Remove trailing zeros
# Ensure we have at least 2 values if we have any
while len(to_values) < 2:
to_values.append(0)
keyframe_line += f' to="[{", ".join(str(v) for v in to_values)}]"'
if has_ti:
ti_values = []
for i in range(3):
val = params[LottieTensor.Index.Keyframe.TI1 + i]
if val > -2000:
ti_values.append(LottieTensor._format_value(val, preserve_int=False))
else:
break # Stop at first padding value
# Only output non-zero values, but always include at least 2 dimensions if any exist
while len(ti_values) > 2 and abs(ti_values[-1]) < 1e-10:
ti_values.pop() # Remove trailing zeros
# Ensure we have at least 2 values if we have any
while len(ti_values) < 2:
ti_values.append(0)
keyframe_line += f' ti="[{", ".join(str(v) for v in ti_values)}]"'
# Check and output e parameter based on context
has_e = params[LottieTensor.Index.Keyframe.E1] > -2000
if has_e: # Output e regardless of hold flag
if current_context == "rotation":
# For rotation, output single e value
e_val = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E1], preserve_int=False)
keyframe_line += f' e="[{e_val}]"'
elif current_context == "scale":
# For scale, output three e values
e1 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E1], preserve_int=False) if params[LottieTensor.Index.Keyframe.E1] > -2000 else 0
e2 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E2], preserve_int=False) if params[LottieTensor.Index.Keyframe.E2] > -2000 else 0
e3 = LottieTensor._format_value(params[LottieTensor.Index.Keyframe.E3], preserve_int=False) if params[LottieTensor.Index.Keyframe.E3] > -2000 else 0
keyframe_line += f' e="[{e1}, {e2}, {e3}]"'
# Add other contexts as needed
keyframe_line += ")"
lines.append(keyframe_line)
elif cmd_idx == LottieTensor.CMD_GROUP:
#name = string_params.get(f"{cmd_key}_name", "Group")
#mn = string_params.get(f"{cmd_key}_mn", "ADBE Vector Group")
ix = int(params[LottieTensor.Index.Group.IX]) if params[LottieTensor.Index.Group.IX] > -2000 else 1
cix = int(params[LottieTensor.Index.Group.CIX]) if params[LottieTensor.Index.Group.CIX] > -2000 else 2
bm = int(params[LottieTensor.Index.Group.BM]) if params[LottieTensor.Index.Group.BM] > -2000 else 0
hd = "true" if params[LottieTensor.Index.Group.HD] > 0.5 else "false"
np = int(params[LottieTensor.Index.Group.NP]) if params[LottieTensor.Index.Group.NP] > -2000 else 0
lines.append(f'({cmd} ix={ix} cix={cix} bm={bm} hd={hd} np={np})')
elif cmd_idx == LottieTensor.CMD_PATH:
#name = string_params.get(f"{cmd_key}_name", "Path")
#mn = string_params.get(f"{cmd_key}_mn", "ADBE Vector Path") # Add mn
ix = int(params[LottieTensor.Index.Path.IX]) if params[LottieTensor.Index.Path.IX] > -2000 else 1
ind = int(params[LottieTensor.Index.Path.IND]) if params[LottieTensor.Index.Path.IND] > -2000 else 0
ks_ix = int(params[LottieTensor.Index.Path.KS_IX]) if params[LottieTensor.Index.Path.KS_IX] > -2000 else 2
closed = "true" if params[LottieTensor.Index.Path.CLOSED] > 0.5 else "false"
hd = "true" if params[LottieTensor.Index.Path.HD] > 0.5 else "false" # Add HD
# Check if path is animated
if params[LottieTensor.Index.Path.ANIMATED] > 0.5:
lines.append(f'({cmd} ix={ix} ind={ind} ks_ix={ks_ix} animated="true" hd={hd})')
current_context = "path"
else:
lines.append(f'({cmd} ix={ix} ind={ind} ks_ix={ks_ix} closed={closed} hd={hd})')
elif cmd_idx == LottieTensor.CMD_POINT:
# Check if this is a valid point (not padding)
if params[LottieTensor.Index.Point.X] > -2000 and params[LottieTensor.Index.Point.Y] > -2000:
x = LottieTensor._format_value(params[LottieTensor.Index.Point.X])
y = LottieTensor._format_value(params[LottieTensor.Index.Point.Y])
in_x = LottieTensor._format_value(params[LottieTensor.Index.Point.IN_X])
in_y = LottieTensor._format_value(params[LottieTensor.Index.Point.IN_Y])
out_x = LottieTensor._format_value(params[LottieTensor.Index.Point.OUT_X])
out_y = LottieTensor._format_value(params[LottieTensor.Index.Point.OUT_Y])
lines.append(f"({cmd} x={x} y={y} in_x={in_x} in_y={in_y} out_x={out_x} out_y={out_y})")
# else: skip padding points
elif cmd_idx == LottieTensor.CMD_FILL:
#name = string_params.get(f"{cmd_key}_name", "Fill")
color_dim = int(params[LottieTensor.Index.Fill.COLOR_DIM]) if params[LottieTensor.Index.Fill.COLOR_DIM] > -2000 else 3
has_c_a = "True" if params[LottieTensor.Index.Fill.HAS_C_A] > 0.5 else "False"
has_c_ix = "True" if params[LottieTensor.Index.Fill.HAS_C_IX] > 0.5 else "False"
c_ix = int(params[LottieTensor.Index.Fill.C_IX]) if params[LottieTensor.Index.Fill.C_IX] > -2000 else 4
bm = int(params[LottieTensor.Index.Fill.BM]) if params[LottieTensor.Index.Fill.BM] > -2000 else 0
fill_rule = int(params[LottieTensor.Index.Fill.FILL_RULE]) if params[LottieTensor.Index.Fill.FILL_RULE] > -2000 else 1
has_o_a = "True" if params[LottieTensor.Index.Fill.HAS_O_A] > 0.5 else "False"
has_o_ix = "True" if params[LottieTensor.Index.Fill.HAS_O_IX] > 0.5 else "False"
o_ix = int(params[LottieTensor.Index.Fill.O_IX]) if params[LottieTensor.Index.Fill.O_IX] > -2000 else 5
color_animated = params[LottieTensor.Index.Fill.COLOR_ANIMATED] > 0.5
opacity_animated = params[LottieTensor.Index.Fill.OPACITY_ANIMATED] > 0.5
line_parts = [f'({cmd}']
# Handle color output
if color_animated:
# Output color keyframes with easing
color_keyframes_json = string_params.get(f"{cmd_key}_color_keyframes", "[]")
color_keyframes = json.loads(color_keyframes_json)
for i, kf in enumerate(color_keyframes):
line_parts.append(f' c_kf_{i}_t={LottieTensor._format_value(kf["t"])}')
line_parts.append(f' c_kf_{i}_r={LottieTensor._format_value(kf["r"]/255)}')
line_parts.append(f' c_kf_{i}_g={LottieTensor._format_value(kf["g"]/255)}')
line_parts.append(f' c_kf_{i}_b={LottieTensor._format_value(kf["b"]/255)}')
# Add easing parameters if they exist and are non-zero (divide by 100 for float output)
if "i_x" in kf and (abs(kf["i_x"]) > 1e-6 or abs(kf.get("i_y", 0)) > 1e-6 or
abs(kf.get("o_x", 0)) > 1e-6 or abs(kf.get("o_y", 0)) > 1e-6):
line_parts.append(f' c_kf_{i}_i_x={LottieTensor._format_value(kf["i_x"] / 100, preserve_int=False)}')
line_parts.append(f' c_kf_{i}_i_y={LottieTensor._format_value(kf.get("i_y", 0) / 100, preserve_int=False)}')
line_parts.append(f' c_kf_{i}_o_x={LottieTensor._format_value(kf.get("o_x", 0) / 100, preserve_int=False)}')
line_parts.append(f' c_kf_{i}_o_y={LottieTensor._format_value(kf.get("o_y", 0) / 100, preserve_int=False)}')
line_parts.append(f' c_kf_count={len(color_keyframes)}')
line_parts.append(' color_animated=true')
else:
# Output static color
r = LottieTensor._format_value(params[LottieTensor.Index.Fill.R]/255)
g = LottieTensor._format_value(params[LottieTensor.Index.Fill.G]/255)
b = LottieTensor._format_value(params[LottieTensor.Index.Fill.B]/255)
line_parts.append(f' r={r} g={g} b={b} color_animated=false')
# Add common color parameters
line_parts.append(f' color_dim={color_dim} has_c_a={has_c_a} has_c_ix={has_c_ix} c_ix={c_ix} bm={bm} fill_rule={fill_rule}')
# Handle opacity output
if opacity_animated:
# Output opacity keyframes (divide by 100 for float output)
opacity_keyframes_json = string_params.get(f"{cmd_key}_opacity_keyframes", "[]")
opacity_keyframes = json.loads(opacity_keyframes_json)
for i, kf in enumerate(opacity_keyframes):
line_parts.append(f' o_kf_{i}_t={LottieTensor._format_value(kf["t"])}')
line_parts.append(f' o_kf_{i}_s={LottieTensor._format_value(kf["s"])}')
line_parts.append(f' o_kf_{i}_i_x={LottieTensor._format_value(kf["i_x"] / 100, preserve_int=False)}')
line_parts.append(f' o_kf_{i}_i_y={LottieTensor._format_value(kf["i_y"] / 100, preserve_int=False)}')
line_parts.append(f' o_kf_{i}_o_x={LottieTensor._format_value(kf["o_x"] / 100, preserve_int=False)}')
line_parts.append(f' o_kf_{i}_o_y={LottieTensor._format_value(kf["o_y"] / 100, preserve_int=False)}')
line_parts.append(f' o_kf_count={len(opacity_keyframes)}')
line_parts.append(' opacity_animated=true')
else:
# Output static opacity
opacity = LottieTensor._format_value(params[LottieTensor.Index.Fill.OPACITY])
line_parts.append(f' opacity={opacity} opacity_animated=false')
# Add opacity-related parameters
line_parts.append(f' has_o_a={has_o_a} has_o_ix={has_o_ix} o_ix={o_ix})')
lines.append(''.join(line_parts))
elif cmd_idx == LottieTensor.CMD_BEZIER:
closed = "true" if params[LottieTensor.Index.Bezier.CLOSED] > 0.5 else "false"
lines.append(f'({cmd} closed={closed})')
elif cmd_idx == LottieTensor.CMD_ELLIPSE:
#name = string_params.get(f"{cmd_key}_name", "Ellipse Path 1")
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_SIZE:
# Check if size is animated - also check for PAD_VAL
if params[LottieTensor.Index.Transform.ANIMATED] > -2000 and params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f'({cmd} animated=true)')
current_context = "size"
else:
# 修改:使用 Transform.X 和 Transform.Y
x = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
y = LottieTensor._format_value(params[LottieTensor.Index.Transform.Y])
lines.append(f'({cmd} {x} {y})')
elif cmd_idx == LottieTensor.CMD_RECT:
#name = string_params.get(f"{cmd_key}_name", "Rectangle Path 1")
hd = "true" if params[LottieTensor.Index.Rect.HD] > 0.5 else "false"
d = int(params[LottieTensor.Index.Rect.D]) if params[LottieTensor.Index.Rect.D] > -2000 else 1
lines.append(f'({cmd} hd={hd} d={d})')
elif cmd_idx == LottieTensor.CMD_ROUNDED:
rounded = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
ix = int(params[LottieTensor.Index.SingleValue.IX]) if params[LottieTensor.Index.SingleValue.IX] > -2000 else 4
lines.append(f'({cmd} {rounded} ix={ix})')
elif cmd_idx == LottieTensor.CMD_TRIM:
#name = string_params.get(f"{cmd_key}_name", "Trim Paths 1")
ix = int(params[LottieTensor.Index.Trim.IX]) if params[LottieTensor.Index.Trim.IX] > -2000 else 1
lines.append(f'({cmd} ix={ix})')
elif cmd_idx == LottieTensor.CMD_END:
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
lines.append(f'({cmd} animated=true)')
current_context = "trim_end" # Set context for keyframes
else:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_START:
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
lines.append(f'({cmd} animated=true)')
current_context = "trim_start" # Set context for keyframes
else:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_OFFSET:
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
lines.append(f'({cmd} animated=true)')
current_context = "trim_offset" # Set context for keyframes
else:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_MULTIPLE:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 1
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_REPEATER:
#name = string_params.get(f"{cmd_key}_name", "Repeater 1")
ix = int(params[LottieTensor.Index.Repeater.IX]) if params[LottieTensor.Index.Repeater.IX] > -2000 else 1
lines.append(f'({cmd} ix={ix})')
elif cmd_idx == LottieTensor.CMD_COPIES:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
ix = int(params[LottieTensor.Index.SingleValue.IX]) if params[LottieTensor.Index.SingleValue.IX] > -2000 else 1
lines.append(f'({cmd} {val} ix={ix})')
elif cmd_idx == LottieTensor.CMD_REPEATER_OFFSET:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
ix = int(params[LottieTensor.Index.SingleValue.IX]) if params[LottieTensor.Index.SingleValue.IX] > -2000 else 2
lines.append(f'({cmd} {val} ix={ix})')
elif cmd_idx == LottieTensor.CMD_COMPOSITE:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 1
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_REPEATER_TRANSFORM:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_TR_P_IX:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 2
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TR_A_IX:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 1
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TR_SCALE:
val1 = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE1])
val2 = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE2])
lines.append(f'({cmd} {val1} {val2})')
elif cmd_idx == LottieTensor.CMD_TR_S_IX:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 3
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TR_R_IX:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 4
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TR_SO_IX:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 5
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TR_EO_IX:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 6
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TRANSFORM_SHAPE:
#name = string_params.get(f"{cmd_key}_name", "Transform")
hd = "true" if params[LottieTensor.Index.TransformShape.HD] > 0.5 else "false"
position_x = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.POSITION_X])
position_y = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.POSITION_Y])
scale_x = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.SCALE_X])
scale_y = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.SCALE_Y])
rotation = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.ROTATION])
opacity = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.OPACITY])
anchor_x = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.ANCHOR_X])
anchor_y = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.ANCHOR_Y])
# Build the output line
line = f'({cmd} hd={hd} position="{position_x} {position_y}" scale="{scale_x} {scale_y}" rotation="{rotation}" opacity="{opacity}" anchor="{anchor_x} {anchor_y}"'
# Only add skew if it's not 0 or PAD_VAL
if params[LottieTensor.Index.TransformShape.SKEW] > -2000 and abs(params[LottieTensor.Index.TransformShape.SKEW]) > 1e-6:
skew = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.SKEW])
line += f' skew="{skew}"'
# Only add skew_axis if it's not 0 or PAD_VAL
if params[LottieTensor.Index.TransformShape.SKEW_AXIS] > -2000 and abs(params[LottieTensor.Index.TransformShape.SKEW_AXIS]) > 1e-6:
skew_axis = LottieTensor._format_value(params[LottieTensor.Index.TransformShape.SKEW_AXIS])
line += f' skew_axis="{skew_axis}"'
line += ')'
lines.append(line)
elif cmd_idx == LottieTensor.CMD_PARENT:
parent_index = int(params[LottieTensor.Index.Parent.PARENT_INDEX]) if params[LottieTensor.Index.Parent.PARENT_INDEX] > -2000 else 0
lines.append(f'({cmd} {parent_index})')
elif cmd_idx == LottieTensor.CMD_ASSET:
# Get tokenizer
tokenizer = LottieTensor.get_tokenizer()
# Decode ID from tokens
id_count = int(params[LottieTensor.Index.Asset.ID_TOKEN_COUNT]) if params[LottieTensor.Index.Asset.ID_TOKEN_COUNT] > -2000 else 0
id_tokens = []
for i in range(id_count):
if params[LottieTensor.Index.Asset.ID_TOKEN_0 + i] > -2000:
id_tokens.append(int(params[LottieTensor.Index.Asset.ID_TOKEN_0 + i]))
asset_id = tokenizer.decode(id_tokens, skip_special_tokens=True) if id_tokens else "comp_0"
fr = LottieTensor._format_value(params[LottieTensor.Index.Asset.FR])
lines.append(f'({cmd} id="{asset_id}" fr={fr})')
elif cmd_idx == LottieTensor.CMD_FONT:
# Get tokenizer
tokenizer = LottieTensor.get_tokenizer()
# Decode family from tokens
family_count = int(params[LottieTensor.Index.Font.FAMILY_TOKEN_COUNT]) if params[LottieTensor.Index.Font.FAMILY_TOKEN_COUNT] > -2000 else 0
family_tokens = []
for i in range(family_count):
if params[LottieTensor.Index.Font.FAMILY_TOKEN_0 + i] > -2000:
family_tokens.append(int(params[LottieTensor.Index.Font.FAMILY_TOKEN_0 + i]))
# Decode style from tokens
style_count = int(params[LottieTensor.Index.Font.STYLE_TOKEN_COUNT]) if params[LottieTensor.Index.Font.STYLE_TOKEN_COUNT] > -2000 else 0
style_tokens = []
for i in range(style_count):
if params[LottieTensor.Index.Font.STYLE_TOKEN_0 + i] > -2000:
style_tokens.append(int(params[LottieTensor.Index.Font.STYLE_TOKEN_0 + i]))
family = tokenizer.decode(family_tokens, skip_special_tokens=True) if family_tokens else ""
style = tokenizer.decode(style_tokens, skip_special_tokens=True) if style_tokens else ""
ascent = LottieTensor._format_value(params[LottieTensor.Index.Font.ASCENT])
lines.append(f'({cmd} family="{family}" style="{style}" ascent={ascent})')
elif cmd_idx == LottieTensor.CMD_CHAR:
# Get tokenizer
tokenizer = LottieTensor.get_tokenizer()
# Decode ch from tokens
ch_count = int(params[LottieTensor.Index.Char.CH_TOKEN_COUNT]) if params[LottieTensor.Index.Char.CH_TOKEN_COUNT] > -2000 else 0
ch_tokens = []
for i in range(ch_count):
if params[LottieTensor.Index.Char.CH_TOKEN_0 + i] > -2000:
ch_tokens.append(int(params[LottieTensor.Index.Char.CH_TOKEN_0 + i]))
# Decode style from tokens
style_count = int(params[LottieTensor.Index.Char.STYLE_TOKEN_COUNT]) if params[LottieTensor.Index.Char.STYLE_TOKEN_COUNT] > -2000 else 0
style_tokens = []
for i in range(style_count):
if params[LottieTensor.Index.Char.STYLE_TOKEN_0 + i] > -2000:
style_tokens.append(int(params[LottieTensor.Index.Char.STYLE_TOKEN_0 + i]))
# Decode family from tokens
family_count = int(params[LottieTensor.Index.Char.FAMILY_TOKEN_COUNT]) if params[LottieTensor.Index.Char.FAMILY_TOKEN_COUNT] > -2000 else 0
family_tokens = []
for i in range(family_count):
if params[LottieTensor.Index.Char.FAMILY_TOKEN_0 + i] > -2000:
family_tokens.append(int(params[LottieTensor.Index.Char.FAMILY_TOKEN_0 + i]))
ch = tokenizer.decode(ch_tokens, skip_special_tokens=True) if ch_tokens else ""
style = tokenizer.decode(style_tokens, skip_special_tokens=True) if style_tokens else ""
family = tokenizer.decode(family_tokens, skip_special_tokens=True) if family_tokens else ""
size = LottieTensor._format_value(params[LottieTensor.Index.Char.SIZE])
w = LottieTensor._format_value(params[LottieTensor.Index.Char.W])
lines.append(f'({cmd} ch="{ch}" size={size} style="{style}" w={w} family="{family}")')
elif cmd_idx == LottieTensor.CMD_TEXT_LAYER:
#name = string_params.get(f"{cmd_key}_name", "Text Layer")
index = LottieTensor._format_value(params[LottieTensor.Index.TextLayer.INDEX])
in_point = LottieTensor._format_value(params[LottieTensor.Index.TextLayer.IN_POINT])
out_point = LottieTensor._format_value(params[LottieTensor.Index.TextLayer.OUT_POINT])
start_time = LottieTensor._format_value(params[LottieTensor.Index.TextLayer.START_TIME])
hasMask = "True" if params[LottieTensor.Index.TextLayer.HAS_MASK] > 0.5 else "False" # 新增
lines.append(f'({cmd} index={index} in_point={in_point} out_point={out_point} start_time={start_time} hasMask={hasMask})') # 修改
#elif cmd_idx == LottieTensor.CMD_TEXT_KEYFRAME:
# t = LottieTensor._format_value(params[LottieTensor.Index.TextKeyframe.T])
# lines.append(f'({cmd} t={t})')
elif cmd_idx == LottieTensor.CMD_FONT_SIZE:
size = LottieTensor._format_value(params[LottieTensor.Index.FontSize.SIZE])
lines.append(f'({cmd} {size})')
elif cmd_idx == LottieTensor.CMD_FONT_FAMILY:
family = string_params.get(f"{cmd_key}_family", "")
lines.append(f'({cmd} "{family}")')
elif cmd_idx == LottieTensor.CMD_TEXT:
text = string_params.get(f"{cmd_key}_text", "")
lines.append(f'({cmd} "{text}")')
elif cmd_idx == LottieTensor.CMD_CA:
value = LottieTensor._format_value(params[LottieTensor.Index.Ca.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_JUSTIFY:
value = LottieTensor._format_value(params[LottieTensor.Index.Justify.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_TRACKING:
value = LottieTensor._format_value(params[LottieTensor.Index.Tracking.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_LINE_HEIGHT:
value = LottieTensor._format_value(params[LottieTensor.Index.LineHeight.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_LETTER_SPACING:
value = LottieTensor._format_value(params[LottieTensor.Index.LetterSpacing.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_FILL_COLOR:
r = LottieTensor._format_value(params[LottieTensor.Index.FillColor.R]/255)
g = LottieTensor._format_value(params[LottieTensor.Index.FillColor.G]/255)
b = LottieTensor._format_value(params[LottieTensor.Index.FillColor.B]/255)
lines.append(f'({cmd} {r} {g} {b})')
elif cmd_idx == LottieTensor.CMD_G:
value = LottieTensor._format_value(params[LottieTensor.Index.G.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_ALIGNMENT:
a = LottieTensor._format_value(params[LottieTensor.Index.Alignment.A])
lines.append(f'({cmd} a={a})')
elif cmd_idx == LottieTensor.CMD_ALIGNMENT_K:
val1 = LottieTensor._format_value(params[LottieTensor.Index.AlignmentK.VALUE1])
val2 = LottieTensor._format_value(params[LottieTensor.Index.AlignmentK.VALUE2])
lines.append(f'({cmd} {val1} {val2})')
elif cmd_idx == LottieTensor.CMD_ALIGNMENT_IX:
value = LottieTensor._format_value(params[LottieTensor.Index.AlignmentIx.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_EFFECTS:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_EFFECT:
#name = string_params.get(f"{cmd_key}_name", "")
match_name = string_params.get(f"{cmd_key}_match_name", "")
type_val = int(params[LottieTensor.Index.Effect.TYPE]) if params[LottieTensor.Index.Effect.TYPE] > -2000 else 0
index = int(params[LottieTensor.Index.Effect.INDEX]) if params[LottieTensor.Index.Effect.INDEX] > -2000 else 1
np = int(params[LottieTensor.Index.Effect.NP]) if params[LottieTensor.Index.Effect.NP] > -2000 else 0
enabled = int(params[LottieTensor.Index.Effect.ENABLED]) if params[LottieTensor.Index.Effect.ENABLED] > -2000 else 1
line = f'({cmd} type={type_val} index={index}'
if np > 0: # Only output np if it's non-zero
line += f' np={np}'
line += f' match_name="{match_name}"'
if enabled != 1: # Only output enabled if it's not the default value
line += f' enabled={enabled}'
line += ')'
lines.append(line)
# Add CMD_LAYER_EFFECT output:
elif cmd_idx == LottieTensor.CMD_LAYER_EFFECT:
#name = string_params.get(f"{cmd_key}_name", "")
match_name = string_params.get(f"{cmd_key}_match_name", "")
index = int(params[LottieTensor.Index.LayerEffect.INDEX]) if params[LottieTensor.Index.LayerEffect.INDEX] > -2000 else 1
value = LottieTensor._format_value(params[LottieTensor.Index.LayerEffect.VALUE])
lines.append(f'({cmd} index={index} value={value} match_name="{match_name}")')
elif cmd_idx == LottieTensor.CMD_DROPDOWN:
#name = string_params.get(f"{cmd_key}_name", "")
index = int(params[LottieTensor.Index.Dropdown.INDEX]) if params[LottieTensor.Index.Dropdown.INDEX] > -2000 else 1
value = int(params[LottieTensor.Index.Dropdown.VALUE]) if params[LottieTensor.Index.Dropdown.VALUE] > -2000 else 0
lines.append(f'({cmd} index={index} value={value})')
elif cmd_idx == LottieTensor.CMD_NO_VALUE:
#@name = string_params.get(f"{cmd_key}_name", "")
index = int(params[LottieTensor.Index.NO_VALUE.INDEX]) if params[LottieTensor.Index.NO_VALUE.INDEX] > -2000 else 1
value = int(params[LottieTensor.Index.NO_VALUE.VALUE]) if params[LottieTensor.Index.NO_VALUE.VALUE] > -2000 else 0
lines.append(f'({cmd} index={index} value={value})')
elif cmd_idx == LottieTensor.CMD_IGNORED:
#name = string_params.get(f"{cmd_key}_name", "")
index = int(params[LottieTensor.Index.Ignored.INDEX]) if params[LottieTensor.Index.Ignored.INDEX] > -2000 else 1
value = LottieTensor._format_value(params[LottieTensor.Index.Ignored.VALUE])
lines.append(f'({cmd} index={index} value={value})')
elif cmd_idx == LottieTensor.CMD_SLIDER:
#name = string_params.get(f"{cmd_key}_name", "")
index = int(params[LottieTensor.Index.Slider.INDEX]) if params[LottieTensor.Index.Slider.INDEX] > -2000 else 1
value = LottieTensor._format_value(params[LottieTensor.Index.Slider.VALUE])
lines.append(f'({cmd} index={index} value={value})')
elif cmd_idx == LottieTensor.CMD_GRADIENT_FILL:
#name = string_params.get(f"{cmd_key}_name", "Gradient Fill 1")
lines.append(f'({cmd})')
current_context = "gradient_fill" # Set context for subsequent commands
elif cmd_idx == LottieTensor.CMD_OPACITY and current_context == "gradient_fill":
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_FILL_RULE:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 1
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_START_POINT:
x = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE1])
y = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE2])
lines.append(f'({cmd} {x} {y})')
elif cmd_idx == LottieTensor.CMD_END_POINT:
x = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE1])
y = LottieTensor._format_value(params[LottieTensor.Index.TwoValues.VALUE2])
lines.append(f'({cmd} {x} {y})')
elif cmd_idx == LottieTensor.CMD_GRADIENT_TYPE:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 1
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_HIGHLIGHT_LENGTH:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_HIGHLIGHT_ANGLE:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_ORIGINAL_COLORS:
count = int(params[LottieTensor.Index.OriginalColors.COUNT]) if params[LottieTensor.Index.OriginalColors.COUNT] > -2000 else 0
color_values = []
for i in range(count):
if params[LottieTensor.Index.OriginalColors.COLOR_0 + i] > -2000:
color_values.append(LottieTensor._format_value(params[LottieTensor.Index.OriginalColors.COLOR_0 + i])/255)
colors_str = ", ".join(str(v) for v in color_values)
lines.append(f'({cmd} [{colors_str}])')
elif cmd_idx == LottieTensor.CMD_COLOR_POINTS:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 2
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_GRADIENT_FILL_END:
lines.append(f'({cmd})')
current_context = None # Reset context
elif cmd_idx == LottieTensor.CMD_GRADIENT_STROKE:
#name = string_params.get(f"{cmd_key}_name", "Gradient Stroke 1")
lines.append(f'({cmd})')
current_context = "gradient_stroke" # Set context for subsequent commands
elif cmd_idx == LottieTensor.CMD_OPACITY and current_context == "gradient_stroke":
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
current_context = "gradient_stroke" # Set context for subsequent commands
elif cmd_idx == LottieTensor.CMD_WIDTH:
if params[LottieTensor.Index.SingleValue.VALUE] > -2000:
# 除以100恢复原值
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE] / 10, preserve_int=False)
lines.append(f'({cmd} {val})')
#if current_context == "gradient_stroke":
# Check if value exists (not PAD_VAL)
# if params[LottieTensor.Index.SingleValue.VALUE] > -2000:
# val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
# lines.append(f'({cmd} {val})')
# else:
# No value, output just the command
# lines.append(f'({cmd})')
#else:
# Handle width in other contexts if needed
# lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_LINE_CAP:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 2
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_LINE_JOIN:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 2
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_MITER_LIMIT:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 0
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_GRADIENT_STROKE_END:
lines.append(f'({cmd})')
current_context = None # Reset context
elif cmd_idx == LottieTensor.CMD_COLOR:
#name = string_params.get(f"{cmd_key}_name", "Color")
index = int(params[LottieTensor.Index.Color.INDEX]) if params[LottieTensor.Index.Color.INDEX] > -2000 else 1
r = LottieTensor._format_value(params[LottieTensor.Index.Color.R]/255)
g = LottieTensor._format_value(params[LottieTensor.Index.Color.G]/255)
b = LottieTensor._format_value(params[LottieTensor.Index.Color.B]/255)
lines.append(f'({cmd} index={index} r={r} g={g} b={b})')
elif cmd_idx == LottieTensor.CMD_MERGE:
#name = string_params.get(f"{cmd_key}_name", "Merge Paths 1")
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_MERGE_MODE:
mode = int(params[LottieTensor.Index.MergeMode.MODE]) if params[LottieTensor.Index.MergeMode.MODE] > -2000 else 1
lines.append(f'({cmd} {mode})')
elif cmd_idx == LottieTensor.CMD_SOLID_LAYER:
#name = string_params.get(f"{cmd_key}_name", "Solid Layer")
#color = string_params.get(f"{cmd_key}_color", "#000000")
r = int(min(255, max(0, params[LottieTensor.Index.SolidLayer.COLOR_R])))
g = int(min(255, max(0, params[LottieTensor.Index.SolidLayer.COLOR_G])))
b = int(min(255, max(0, params[LottieTensor.Index.SolidLayer.COLOR_B])))
a = int(min(255, max(0, params[LottieTensor.Index.SolidLayer.COLOR_A])))
# You can use RGB values directly or convert back to hex if needed
color_rgb = (r, g, b, a)
# Or convert back to hex format if required:
color = f"#{r:02x}{g:02x}{b:02x}{a:02x}"
index = LottieTensor._format_value(params[LottieTensor.Index.SolidLayer.INDEX])
in_point = LottieTensor._format_value(params[LottieTensor.Index.SolidLayer.IN_POINT])
out_point = LottieTensor._format_value(params[LottieTensor.Index.SolidLayer.OUT_POINT])
start_time = LottieTensor._format_value(params[LottieTensor.Index.SolidLayer.START_TIME])
width = LottieTensor._format_value(params[LottieTensor.Index.SolidLayer.WIDTH])
height = LottieTensor._format_value(params[LottieTensor.Index.SolidLayer.HEIGHT])
hasMask = "True" if params[LottieTensor.Index.SolidLayer.HAS_MASK] > 0.5 else "False"
lines.append(f'({cmd} index={index} in_point={in_point} out_point={out_point} start_time={start_time} color="{color}" width={width} height={height} hasMask={hasMask})')
elif cmd_idx == LottieTensor.CMD_MASKS_PROPERTIES:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_MASK:
#nm = string_params.get(f"{cmd_key}_nm", "Mask 1")
index = int(params[LottieTensor.Index.Mask.INDEX]) if params[LottieTensor.Index.Mask.INDEX] > -2000 else 0
inv = "true" if params[LottieTensor.Index.Mask.INV] > 0.5 else "false"
# Convert mode value back to string
mode_val = int(params[LottieTensor.Index.Mask.MODE]) if params[LottieTensor.Index.Mask.MODE] > -2000 else 0
mode_map = {0: "a", 1: "s", 2: "i", 3: "n"}
mode = mode_map.get(mode_val, "a")
lines.append(f'({cmd} index={index} inv={inv} mode={mode})')
elif cmd_idx == LottieTensor.CMD_MASK_PT:
a = int(params[LottieTensor.Index.MaskPt.A]) if params[LottieTensor.Index.MaskPt.A] > -2000 else 1
ix = int(params[LottieTensor.Index.MaskPt.IX]) if params[LottieTensor.Index.MaskPt.IX] > -2000 else 1
lines.append(f'({cmd} a={a} ix={ix})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_K_C:
c = "true" if params[LottieTensor.Index.MaskPtK.C] > 0.5 else "false"
lines.append(f'({cmd} {c})') # Changed from c={c} to just {c}
elif cmd_idx in [LottieTensor.CMD_MASK_PT_K_I, LottieTensor.CMD_MASK_PT_K_O, LottieTensor.CMD_MASK_PT_K_V]:
count = int(params[LottieTensor.Index.MaskPtKValues.COUNT]) if params[LottieTensor.Index.MaskPtKValues.COUNT] > -2000 else 0
if count == 0:
# Fallback: find last non-padding value
for i in range(19, -1, -1):
val = params[LottieTensor.Index.MaskPtKValues.V1 + i]
if val > -2000:
count = i + 1
break
values = []
for i in range(count):
val = params[LottieTensor.Index.MaskPtKValues.V1 + i]
if val > -2000:
values.append(LottieTensor._format_value(val))
else:
values.append(0.0)
lines.append(f'({cmd} {" ".join(str(v) for v in values)})')
elif cmd_idx == LottieTensor.CMD_MASK_O:
a = int(params[LottieTensor.Index.MaskO.A]) if params[LottieTensor.Index.MaskO.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.MaskO.K]) if params[LottieTensor.Index.MaskO.K] > -2000 else 100
ix = int(params[LottieTensor.Index.MaskO.IX]) if params[LottieTensor.Index.MaskO.IX] > -2000 else 3
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_MASK_X:
a = int(params[LottieTensor.Index.MaskX.A]) if params[LottieTensor.Index.MaskX.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.MaskX.K]) if params[LottieTensor.Index.MaskX.K] > -2000 else 0
ix = int(params[LottieTensor.Index.MaskX.IX]) if params[LottieTensor.Index.MaskX.IX] > -2000 else 4
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_MASK_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_K:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_K_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_MASKS_PROPERTIES_END:
lines.append(f'({cmd})')
elif cmd_idx in [LottieTensor.CMD_MASK_PT_K_ARRAY, LottieTensor.CMD_MASK_PT_K_ARRAY_END,
LottieTensor.CMD_MASK_PT_KF_S, LottieTensor.CMD_MASK_PT_KF_S_END,
LottieTensor.CMD_MASK_PT_KF_SHAPE_END, LottieTensor.CMD_MASK_PT_KEYFRAME_END]:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_KEYFRAME:
index = int(params[LottieTensor.Index.MaskPtKeyframe.INDEX]) if params[LottieTensor.Index.MaskPtKeyframe.INDEX] > -2000 else 0
t = LottieTensor._format_value(params[LottieTensor.Index.MaskPtKeyframe.T])
lines.append(f'({cmd} index={index} t={t})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_KF_I:
x = LottieTensor._format_value(params[LottieTensor.Index.MaskPtKfI.X])
y = LottieTensor._format_value(params[LottieTensor.Index.MaskPtKfI.Y])
lines.append(f'({cmd} x={x} y={y})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_KF_O:
x = LottieTensor._format_value(params[LottieTensor.Index.MaskPtKfO.X])
y = LottieTensor._format_value(params[LottieTensor.Index.MaskPtKfO.Y])
lines.append(f'({cmd} x={x} y={y})')
elif cmd_idx == LottieTensor.CMD_MASK_PT_KF_SHAPE:
index = int(params[LottieTensor.Index.MaskPtKfShape.INDEX]) if params[LottieTensor.Index.MaskPtKfShape.INDEX] > -2000 else 0
c = "true" if params[LottieTensor.Index.MaskPtKfShape.C] > 0.5 else "false"
lines.append(f'({cmd} index={index} c={c})')
elif cmd_idx in [LottieTensor.CMD_MASK_PT_KF_SHAPE_I, LottieTensor.CMD_MASK_PT_KF_SHAPE_O,
LottieTensor.CMD_MASK_PT_KF_SHAPE_V]:
# Get the count from params, not from string_params
count = int(params[LottieTensor.Index.MaskPtKfShapeValues.COUNT]) if params[LottieTensor.Index.MaskPtKfShapeValues.COUNT] > -2000 else 0
if count == 0:
# If no count stored, find the last non-padding value
for i in range(19, -1, -1): # Check V1 through V20
if i < LottieTensor.PARAM_DIM:
val = params[LottieTensor.Index.MaskPtKfShapeValues.V1 + i]
if val > -2000:
count = i + 1
break
if count == 0:
count = 8 # Default to 8 if no valid values found
# Output the exact number of values
values = []
for i in range(count):
if i < 20:
val = params[LottieTensor.Index.MaskPtKfShapeValues.V1 + i]
if val > -2000:
values.append(LottieTensor._format_value(val))
else:
values.append(0.0)
else:
values.append(0.0)
lines.append(f'({cmd} {" ".join(str(v) for v in values)})')
elif cmd_idx == LottieTensor.CMD_TR_POSITION:
x = LottieTensor._format_value(params[LottieTensor.Index.TrPosition.X])
y = LottieTensor._format_value(params[LottieTensor.Index.TrPosition.Y])
lines.append(f'({cmd} {x} {y})')
elif cmd_idx == LottieTensor.CMD_TR_ANCHOR:
x = LottieTensor._format_value(params[LottieTensor.Index.TrAnchor.X])
y = LottieTensor._format_value(params[LottieTensor.Index.TrAnchor.Y])
lines.append(f'({cmd} {x} {y})')
elif cmd_idx == LottieTensor.CMD_TR_ROTATION:
val = LottieTensor._format_value(params[LottieTensor.Index.TrRotation.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TR_START_OPACITY:
val = LottieTensor._format_value(params[LottieTensor.Index.TrStartOpacity.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_TR_END_OPACITY:
if params[LottieTensor.Index.TrEndOpacity.VALUE] > -2000:
val = LottieTensor._format_value(params[LottieTensor.Index.TrEndOpacity.VALUE])
lines.append(f'({cmd} {val})')
else:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_ZIG_ZAG:
#name = string_params.get(f"{cmd_key}_name", "Zig Zag 1")
ix = int(params[LottieTensor.Index.ZigZag.IX]) if params[LottieTensor.Index.ZigZag.IX] > -2000 else 2
lines.append(f'({cmd} ix={ix})')
elif cmd_idx == LottieTensor.CMD_FREQUENCY:
value = LottieTensor._format_value(params[LottieTensor.Index.Frequency.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_AMPLITUDE:
value = LottieTensor._format_value(params[LottieTensor.Index.Amplitude.VALUE])
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_POINT_TYPE:
value = int(params[LottieTensor.Index.PointType.VALUE]) if params[LottieTensor.Index.PointType.VALUE] > -2000 else 2
lines.append(f'({cmd} {value})')
elif cmd_idx == LottieTensor.CMD_ZIG_ZAG_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_ANIMATORS:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_ANIMATOR:
#nm = string_params.get(f"{cmd_key}_nm", "Animator 1")
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_RANGE_SELECTOR:
t = int(params[LottieTensor.Index.RangeSelector.T]) if params[LottieTensor.Index.RangeSelector.T] > -2000 else 0
r = int(params[LottieTensor.Index.RangeSelector.R]) if params[LottieTensor.Index.RangeSelector.R] > -2000 else 1
b = int(params[LottieTensor.Index.RangeSelector.B]) if params[LottieTensor.Index.RangeSelector.B] > -2000 else 1
sh = int(params[LottieTensor.Index.RangeSelector.SH]) if params[LottieTensor.Index.RangeSelector.SH] > -2000 else 1
rn = int(params[LottieTensor.Index.RangeSelector.RN]) if params[LottieTensor.Index.RangeSelector.RN] > -2000 else 0
lines.append(f'({cmd} t={t} r={r} b={b} sh={sh} rn={rn})')
elif cmd_idx == LottieTensor.CMD_RANGE_START:
a = int(params[LottieTensor.Index.RangeStart.A]) if params[LottieTensor.Index.RangeStart.A] > -2000 else 0
lines.append(f'({cmd} a={a})')
if a > 0.5:
current_context = "range_start"
elif cmd_idx == LottieTensor.CMD_RANGE_START_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.RangeStartKeyframe.T])
s = LottieTensor._format_value(params[LottieTensor.Index.RangeStartKeyframe.S])
# Check if this is the last keyframe (no easing values or all zeros)
i_x = params[LottieTensor.Index.RangeStartKeyframe.I_X]/100
i_y = params[LottieTensor.Index.RangeStartKeyframe.I_Y]/100
o_x = params[LottieTensor.Index.RangeStartKeyframe.O_X]/100
o_y = params[LottieTensor.Index.RangeStartKeyframe.O_Y]/100
has_meaningful_easing = (
(i_x > -2000 and abs(i_x) > 1e-6) or
(i_y > -2000 and abs(i_y) > 1e-6) or
(o_x > -2000 and abs(o_x) > 1e-6) or
(o_y > -2000 and abs(o_y) > 1e-6)
)
if has_meaningful_easing:
i_x_val = LottieTensor._format_value(i_x, preserve_int=False) if i_x > -2000 else 0
i_y_val = LottieTensor._format_value(i_y, preserve_int=False) if i_y > -2000 else 0
o_x_val = LottieTensor._format_value(o_x, preserve_int=False) if o_x > -2000 else 0
o_y_val = LottieTensor._format_value(o_y, preserve_int=False) if o_y > -2000 else 0
lines.append(f'({cmd} t={t} s={s} i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val})')
else:
lines.append(f'({cmd} t={t} s={s})')
elif cmd_idx == LottieTensor.CMD_RANGE_START_END:
lines.append(f'({cmd})')
current_context = None
elif cmd_idx == LottieTensor.CMD_AMOUNT:
a = int(params[LottieTensor.Index.Amount.A]) if params[LottieTensor.Index.Amount.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.Amount.K]) if params[LottieTensor.Index.Amount.K] > -2000 else 100
ix = int(params[LottieTensor.Index.Amount.IX]) if params[LottieTensor.Index.Amount.IX] > -2000 else 4
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_MAX_EASE:
a = int(params[LottieTensor.Index.MaxEase.A]) if params[LottieTensor.Index.MaxEase.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.MaxEase.K]) if params[LottieTensor.Index.MaxEase.K] > -2000 else 0
ix = int(params[LottieTensor.Index.MaxEase.IX]) if params[LottieTensor.Index.MaxEase.IX] > -2000 else 7
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_MIN_EASE:
a = int(params[LottieTensor.Index.MinEase.A]) if params[LottieTensor.Index.MinEase.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.MinEase.K]) if params[LottieTensor.Index.MinEase.K] > -2000 else 0
ix = int(params[LottieTensor.Index.MinEase.IX]) if params[LottieTensor.Index.MinEase.IX] > -2000 else 8
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_ANIMATOR_PROPERTIES:
lines.append(f'({cmd})')
current_context = "animator_properties"
elif cmd_idx == LottieTensor.CMD_ANIMATOR_PROPERTIES_END:
lines.append(f'({cmd})')
current_context = None
elif cmd_idx == LottieTensor.CMD_OPACITY and current_context == "animator_properties":
# Special handling for opacity within animator_properties
a = int(params[LottieTensor.Index.Amount.A]) if params[LottieTensor.Index.Amount.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.Amount.K]) if params[LottieTensor.Index.Amount.K] > -2000 else 0
ix = int(params[LottieTensor.Index.Amount.IX]) if params[LottieTensor.Index.Amount.IX] > -2000 else 9
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_RANGE_SELECTOR_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_ANIMATOR_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_ANIMATORS_END:
lines.append(f'({cmd})')
elif cmd_idx == LottieTensor.CMD_RADIUS:
val = LottieTensor._format_value(params[LottieTensor.Index.Radius.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_RANGE_END:
a = int(params[LottieTensor.Index.RangeEnd.A]) if params[LottieTensor.Index.RangeEnd.A] > -2000 else 0
lines.append(f'({cmd} a={a})')
if a > 0.5:
current_context = "range_end"
elif cmd_idx == LottieTensor.CMD_RANGE_END_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.RangeEndKeyframe.T])
s = LottieTensor._format_value(params[LottieTensor.Index.RangeEndKeyframe.S])
# Check if this is the last keyframe (no easing values or all zeros)
i_x = params[LottieTensor.Index.RangeEndKeyframe.I_X]/100
i_y = params[LottieTensor.Index.RangeEndKeyframe.I_Y]/100
o_x = params[LottieTensor.Index.RangeEndKeyframe.O_X]/100
o_y = params[LottieTensor.Index.RangeEndKeyframe.O_Y]/100
has_meaningful_easing = (
(i_x > -2000 and abs(i_x) > 1e-6) or
(i_y > -2000 and abs(i_y) > 1e-6) or
(o_x > -2000 and abs(o_x) > 1e-6) or
(o_y > -2000 and abs(o_y) > 1e-6)
)
if has_meaningful_easing:
i_x_val = LottieTensor._format_value(i_x, preserve_int=False) if i_x > -2000 else 0
i_y_val = LottieTensor._format_value(i_y, preserve_int=False) if i_y > -2000 else 0
o_x_val = LottieTensor._format_value(o_x, preserve_int=False) if o_x > -2000 else 0
o_y_val = LottieTensor._format_value(o_y, preserve_int=False) if o_y > -2000 else 0
lines.append(f'({cmd} t={t} s={s} i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val})')
else:
lines.append(f'({cmd} t={t} s={s})')
elif cmd_idx == LottieTensor.CMD_RANGE_END_END:
lines.append(f'({cmd})')
current_context = None
# Fix position output in animator_properties context
elif cmd_idx == LottieTensor.CMD_POSITION and current_context == "animator_properties":
# Special handling for position within animator_properties
a = int(params[LottieTensor.Index.Amount.A]) if params[LottieTensor.Index.Amount.A] > -2000 else 0
ix = int(params[LottieTensor.Index.Amount.IX]) if params[LottieTensor.Index.Amount.IX] > -2000 else 2
# Check if we have array values stored
x = params[LottieTensor.Index.Transform.X]
y = params[LottieTensor.Index.Transform.Y]
z = params[LottieTensor.Index.Transform.Z]
if x > -2000 or y > -2000: # Changed condition - check x or y
# Format as array
x_val = LottieTensor._format_value(x) if x > -2000 else 0
y_val = LottieTensor._format_value(y) if y > -2000 else 0
# Only include z if it's meaningful
if z > -2000 and abs(z) > 1e-6:
lines.append(f'({cmd} a={a} k=[{x_val}, {y_val}, {LottieTensor._format_value(z)}] ix={ix})')
else:
lines.append(f'({cmd} a={a} k=[{x_val}, {y_val}, 0] ix={ix})')
else:
# Format as single value
k = LottieTensor._format_value(params[LottieTensor.Index.Amount.K]) if params[LottieTensor.Index.Amount.K] > -2000 else 0
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_ML2:
val = int(params[LottieTensor.Index.SingleValue.VALUE]) if params[LottieTensor.Index.SingleValue.VALUE] > -2000 else 4
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_RANGE_OFFSET_KEYFRAME:
t = LottieTensor._format_value(params[LottieTensor.Index.RangeOffsetKeyframe.T])
s = LottieTensor._format_value(params[LottieTensor.Index.RangeOffsetKeyframe.S])
# Check if this is the last keyframe (no easing values or all zeros)
i_x = params[LottieTensor.Index.RangeOffsetKeyframe.I_X]/100
i_y = params[LottieTensor.Index.RangeOffsetKeyframe.I_Y]/100
o_x = params[LottieTensor.Index.RangeOffsetKeyframe.O_X]/100
o_y = params[LottieTensor.Index.RangeOffsetKeyframe.O_Y]/100
has_meaningful_easing = (
(i_x > -2000 and abs(i_x) > 1e-6) or
(i_y > -2000 and abs(i_y) > 1e-6) or
(o_x > -2000 and abs(o_x) > 1e-6) or
(o_y > -2000 and abs(o_y) > 1e-6)
)
if has_meaningful_easing:
i_x_val = LottieTensor._format_value(i_x, preserve_int=False) if i_x > -2000 else 0
i_y_val = LottieTensor._format_value(i_y, preserve_int=False) if i_y > -2000 else 0
o_x_val = LottieTensor._format_value(o_x, preserve_int=False) if o_x > -2000 else 0
o_y_val = LottieTensor._format_value(o_y, preserve_int=False) if o_y > -2000 else 0
lines.append(f'({cmd} t={t} s={s} i_x={i_x_val} i_y={i_y_val} o_x={o_x_val} o_y={o_y_val})')
else:
lines.append(f'({cmd} t={t} s={s})')
elif cmd_idx == LottieTensor.CMD_RANGE_OFFSET_END:
lines.append(f'({cmd})')
current_context = None
elif cmd_idx == LottieTensor.CMD_S_M:
a = int(params[LottieTensor.Index.SM.A]) if params[LottieTensor.Index.SM.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.SM.K]) if params[LottieTensor.Index.SM.K] > -2000 else 100
ix = int(params[LottieTensor.Index.SM.IX]) if params[LottieTensor.Index.SM.IX] > -2000 else 6
lines.append(f'({cmd} a={a} k={k} ix={ix})')
# 修改 CMD_OPACITY_ANIMATORS 的输出:
elif cmd_idx == LottieTensor.CMD_OPACITY_ANIMATORS:
a = int(params[LottieTensor.Index.OpacityAnimators.A]) if params[LottieTensor.Index.OpacityAnimators.A] > -2000 else 0
if a > 0.5:
# Animated case - only output a
lines.append(f'({cmd} a={a})')
current_context = "opacity_animators"
else:
# Static case with k value
k = LottieTensor._format_value(params[LottieTensor.Index.OpacityAnimators.K]) if params[LottieTensor.Index.OpacityAnimators.K] > -2000 else 0
ix = int(params[LottieTensor.Index.OpacityAnimators.IX]) if params[LottieTensor.Index.OpacityAnimators.IX] > -2000 else 9
lines.append(f'({cmd} a={a} k={k} ix={ix})')
# 添加 CMD_POSITION_ANIMATORS 的输出:
elif cmd_idx == LottieTensor.CMD_POSITION_ANIMATORS:
a = int(params[LottieTensor.Index.PositionAnimators.A]) if params[LottieTensor.Index.PositionAnimators.A] > -2000 else 0
ix = int(params[LottieTensor.Index.PositionAnimators.IX]) if params[LottieTensor.Index.PositionAnimators.IX] > -2000 else 2
if a > 0.5:
# Animated case
lines.append(f'({cmd} a={a})')
current_context = "position_animators"
else:
# Static case with k value
k_x = params[LottieTensor.Index.PositionAnimators.K_X]
k_y = params[LottieTensor.Index.PositionAnimators.K_Y]
k_z = params[LottieTensor.Index.PositionAnimators.K_Z]
if k_x > -2000 and k_y > -2000:
# Format as array
x_val = LottieTensor._format_value(k_x) if k_x > -2000 else 0
y_val = LottieTensor._format_value(k_y) if k_y > -2000 else 0
z_val = LottieTensor._format_value(k_z) if k_z > -2000 else 0
lines.append(f'({cmd} a={a} k=[{x_val}, {y_val}, {z_val}] ix={ix})')
else:
lines.append(f'({cmd} a={a} k=[0.0, 0.0, 0.0] ix={ix})')
# 添加 CMD_TRACKING_ANIMATORS 的输出:
elif cmd_idx == LottieTensor.CMD_TRACKING_ANIMATORS:
a = int(params[LottieTensor.Index.TrackingAnimators.A]) if params[LottieTensor.Index.TrackingAnimators.A] > -2000 else 0
k = LottieTensor._format_value(params[LottieTensor.Index.TrackingAnimators.K]) if params[LottieTensor.Index.TrackingAnimators.K] > -2000 else 0
ix = int(params[LottieTensor.Index.TrackingAnimators.IX]) if params[LottieTensor.Index.TrackingAnimators.IX] > -2000 else 89
if a > 0.5:
# Animated case
lines.append(f'({cmd} a={a})')
current_context = "tracking_animators"
else:
# Static case
lines.append(f'({cmd} a={a} k={k} ix={ix})')
# 添加结束命令的输出:
elif cmd_idx == LottieTensor.CMD_POSITION_ANIMATORS_END:
lines.append(f'({cmd})')
current_context = None
# Add output formatting (after CMD_OPACITY_ANIMATORS, around line 5590)
elif cmd_idx == LottieTensor.CMD_SCALE_ANIMATORS:
a = int(params[LottieTensor.Index.ScaleAnimators.A]) if params[LottieTensor.Index.ScaleAnimators.A] > -2000 else 0
if a > 0.5:
# Animated case
lines.append(f'({cmd} a={a})')
current_context = "scale_animators"
else:
# Static case with k value
k_x = params[LottieTensor.Index.ScaleAnimators.K_X]
k_y = params[LottieTensor.Index.ScaleAnimators.K_Y]
k_z = params[LottieTensor.Index.ScaleAnimators.K_Z]
if k_x > -2000 and k_y > -2000 and k_z > -2000:
# Check if all values are the same
if abs(k_x - k_y) < 1e-6 and abs(k_y - k_z) < 1e-6:
# Output single value
lines.append(f'({cmd} a={a} k={LottieTensor._format_value(k_x)})')
else:
# Output array
lines.append(f'({cmd} a={a} k=[{LottieTensor._format_value(k_x)}, {LottieTensor._format_value(k_y)}, {LottieTensor._format_value(k_z)}])')
else:
lines.append(f'({cmd} a={a} k=100)')
elif cmd_idx == LottieTensor.CMD_SCALE_ANIMATORS_END:
lines.append(f'({cmd})')
current_context = None
elif cmd_idx == LottieTensor.CMD_ROTATION_ANIMATORS:
a = int(params[LottieTensor.Index.RotationAnimators.A]) if params[LottieTensor.Index.RotationAnimators.A] > -2000 else 0
if a > 0.5:
# Animated case
lines.append(f'({cmd} a={a})')
current_context = "rotation_animators"
else:
# Static case with k value
k = LottieTensor._format_value(params[LottieTensor.Index.RotationAnimators.K]) if params[LottieTensor.Index.RotationAnimators.K] > -2000 else 0
lines.append(f'({cmd} a={a} k={k})')
elif cmd_idx == LottieTensor.CMD_WIDTH_ANIMATED:
# This is a standalone width_animated command
# The context should already be set from the stroke command
# No parameters needed for this command
pass
elif cmd_idx == LottieTensor.CMD_RANGE_OFFSET:
# Use Amount indices for range_offset
a = int(params[LottieTensor.Index.Amount.A]) if params[LottieTensor.Index.Amount.A] > -2000 else 0
if a > 0.5: # This should be checking a, not params[LottieTensor.Index.Amount.A] again
# Animated case - only output a
lines.append(f'({cmd} a={a})')
current_context = "range_offset"
else:
# Static case - output a, k, and ix
k = LottieTensor._format_value(params[LottieTensor.Index.Amount.K]) if params[LottieTensor.Index.Amount.K] > -2000 else 0
ix = int(params[LottieTensor.Index.Amount.IX]) if params[LottieTensor.Index.Amount.IX] > -2000 else 3
lines.append(f'({cmd} a={a} k={k} ix={ix})')
elif cmd_idx == LottieTensor.CMD_ROTATION_ANIMATORS_END:
lines.append(f'({cmd})')
current_context = None
elif cmd_idx == LottieTensor.CMD_RECT_SIZE:
# Output rect_size with two values
if params[LottieTensor.Index.Transform.ANIMATED] > -2000 and params[LottieTensor.Index.Transform.ANIMATED] > 0.5:
lines.append(f'({cmd} animated=true)')
current_context = "size"
else:
# 修改:使用 Transform.X 和 Transform.Y
val1 = LottieTensor._format_value(params[LottieTensor.Index.Transform.X])
val2 = LottieTensor._format_value(params[LottieTensor.Index.Transform.Y])
lines.append(f'({cmd} {val1} {val2})')
elif cmd_idx == LottieTensor.CMD_ELLIPSE_SIZE:
# Output rect_size with two values
# 检查是否是动画
animated_val = params[LottieTensor.Index.Transform.ANIMATED]
if animated_val > -2000 and animated_val > 0.5:
lines.append(f'({cmd} animated=true)')
current_context = "size" # 确保设置上下文
else:
# 静态值 - 检查 X 和 Y 是否为有效值
x_val = params[LottieTensor.Index.Transform.X]
y_val = params[LottieTensor.Index.Transform.Y]
# 如果 X 和 Y 都是默认值 0 且 ANIMATED 未设置,可能是数据丢失
val1 = LottieTensor._format_value(x_val if x_val > -2000 else 0)
val2 = LottieTensor._format_value(y_val if y_val > -2000 else 0)
lines.append(f'({cmd} {val1} {val2})')
elif cmd_idx == LottieTensor.CMD_RECT_ROUNDED:
if params[LottieTensor.Index.SingleValue.ANIMATED] > 0.5:
lines.append(f'({cmd} animated=true)')
current_context = "rect_rounded" # Set context for keyframes
else:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_RECT_ROUNDED_END:
lines.append(f'({cmd})')
current_context = None
elif cmd_idx == LottieTensor.CMD_SKEW:
if params[LottieTensor.Index.SingleValue.VALUE] > -2000:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
elif cmd_idx == LottieTensor.CMD_SKEW_AXIS:
if params[LottieTensor.Index.SingleValue.VALUE] > -2000:
val = LottieTensor._format_value(params[LottieTensor.Index.SingleValue.VALUE])
lines.append(f'({cmd} {val})')
else:
# Default case for any unhandled commands
lines.append(f"({cmd})")
return '\n'.join(lines)
# Keep other methods unchanged
def to_tensor(self) -> torch.Tensor:
"""Convert LottieTensor to a single tensor"""
return torch.cat([self.commands.float(), self.params], dim=1)
@staticmethod
def from_tensor(tensor: torch.Tensor) -> 'LottieTensor':
"""Create LottieTensor from tensor"""
commands = tensor[:, 0:1].long()
params = tensor[:, 1:1+LottieTensor.PARAM_DIM].float()
return LottieTensor(commands, params, PAD_VAL=-2001)
def add_sos(self):
"""Add start-of-sequence token"""
self.commands = torch.cat([self.sos_token, self.commands])
pad_params = torch.ones((1, self.PARAM_DIM)) * self.PAD_VAL
self.params = torch.cat([pad_params, self.params])
self.seq_len += 1
return self
def add_eos(self):
"""Add end-of-sequence token"""
self.commands = torch.cat([self.commands, self.eos_token])
pad_params = torch.ones((1, self.PARAM_DIM)) * self.PAD_VAL
self.params = torch.cat([self.params, pad_params])
self.seq_len += 1
return self
def pad(self, seq_len: int):
"""Pad sequence to specified length"""
pad_len = max(seq_len - len(self.commands), 0)
if pad_len > 0:
pad_commands = torch.ones((pad_len, 1)) * LottieTensor.CMD_PAD
pad_params = torch.ones((pad_len, self.PARAM_DIM)) * self.PAD_VAL
self.commands = torch.cat([self.commands, pad_commands.long()])
self.params = torch.cat([self.params, pad_params])
return self
@staticmethod
def _clamp_value(value: float, min_val: float = -2000, max_val: float = 2000) -> float:
"""Clamp a value between min and max bounds"""
return max(min_val, min(max_val, value))
@staticmethod
def _index_clamp_value(value: float, min_val: float = -100, max_val: float = 100) -> float:
"""Clamp a value between min and max bounds"""
return max(min_val, min(max_val, value))
@classmethod
def init_tokenizer(cls, model_path=None):
"""Initialize tokenizer once for the class - 支持多路径fallback"""
if cls.tokenizer is None:
from transformers import AutoTokenizer
if model_path is None:
# 尝试多个可能的路径
possible_paths = [
'/mnt/jfs-test/Qwen2.5-VL-3B-Instruct',
'/data/models/Qwen2.5-VL-3B-Instruct',
'Qwen/Qwen2.5-VL-3B-Instruct', # HuggingFace Hub
]
for path in possible_paths:
try:
cls.tokenizer = AutoTokenizer.from_pretrained(path)
# 只在主进程打印一次
import os
if os.environ.get('RANK', '0') == '0':
print(f"Tokenizer loaded successfully from: {path}")
return
except Exception as e:
continue
raise ValueError(f"Failed to load tokenizer from any known path: {possible_paths}")
else:
cls.tokenizer = AutoTokenizer.from_pretrained(model_path)
@classmethod
def get_tokenizer(cls):
if cls.tokenizer is None:
from transformers import AutoTokenizer
cls.tokenizer = AutoTokenizer.from_pretrained('/mnt/jfs-test/Qwen2.5-VL-3B-Instruct')
return cls.tokenizer
@staticmethod
def get_param_offset(cmd_idx: int, param_idx: int) -> int:
"""
Get the offset for a parameter based on its command and parameter index.
Returns the offset to add to the parameter value.
"""
# 1. 查缓存
cache_key = (cmd_idx, param_idx)
if cache_key in LottieTensor._OFFSET_CACHE:
return LottieTensor._OFFSET_CACHE[cache_key]
# 更新后的offset范围,确保没有overlap
TIME_OFFSET = 155000 # -2000 to 2000: range [153000, 157000] (4001 values)
SPACE_OFFSET = 159100 # -2000 to 2000: range [157100, 161100] (4001 values)
AMPLITUDE_OFFSET = 161200 # 0 to 20: range [161200, 161220] (21 values)
ANCHOR_OFFSET = 161300 # -2000 to 2000: range [161300, 165300] (4001 values)
ANIMATED_OFFSET = 165400 # 0 to 1: range [165400, 165401] (2 values)
H_FLAG_OFFSET = 165402 # 0 to 1: range [165402, 165403] (2 values)
OFFSET_VAL_OFFSET = 165404 # 0 to 1: range [165404, 165405] (2 values)
CA_OFFSET = 165406 # 0 to 2: range [165406, 165408] (3 values)
JUSTIFY_OFFSET = 165409 # 0 to 6: range [165409, 165415] (7 values)
TEXT_TRACKING_OFFSET = 165416 # -100 to 500: range [165416, 166016] (601 values)
HAS_STROKE_COLOR_OFFSET = 166017 # 0 to 1: range [166017, 166018] (2 values)
IX_OFFSET = 166019 # 0 to 1000: range [166019, 167019] (1001 values)
BM_OFFSET = 167020 # 0 to 20: range [167020, 167040] (21 values)
CLOSED_OFFSET = 167041 # 0 to 1: range [167041, 167042] (2 values)
DIRECTION_OFFSET = 167043 # 0 to 5: range [167043, 167048] (6 values)
STAR_TYPE_OFFSET = 167049 # 0 to 5: range [167049, 167054] (6 values)
MULTIPLE_OFFSET = 167055 # 0 to 5: range [167055, 167060] (6 values)
COMPOSITE_OFFSET = 167061 # 0 to 5: range [167061, 167066] (6 values)
SKEW_OFFSET = 167067 # -25 to 25: range [167067, 167117] (51 values)
SKEW_AXIS_OFFSET = 167118 # -25 to 25: range [167118, 167168] (51 values)
SCALE_OFFSET = 167169 # -1000 to 2000: range [167169, 170169] (3001 values)
ROTATION_OFFSET = 170170 # -720 to 720: range [170170, 171610] (1441 values)
EASE_OFFSET = 171611 # -100 to 100: range [171611, 171811] (201 values)
SMOOTH_OFFSET = 171812 # 0 to 100: range [171812, 171912] (101 values)
TRACKING_OFFSET = 171913 # -50 to 50: range [171913, 172013] (101 values)
INDEX_OFFSET = 172014 # 0 to 1000: range [172014, 173014] (1001 values)
DDD_OFFSET = 173015 # 0 to 1: range [173015, 173016] (2 values)
HD_OFFSET = 173017 # 0 to 1: range [173017, 173018] (2 values)
CP_OFFSET = 173019 # 0 to 50: range [173019, 173069] (51 values)
HAS_MASK_OFFSET = 173070 # 0 to 1: range [173070, 173071] (2 values)
AO_OFFSET = 173072 # 0 to 1: range [173072, 173073] (2 values)
TT_OFFSET = 173074 # 0 to 5: range [173074, 173079] (6 values)
TP_OFFSET = 173080 # 0 to 100: range [173080, 173180] (101 values)
TD_OFFSET = 173181 # 0 to 2: range [173181, 173183] (3 values)
CT_OFFSET = 173184 # 0 to 1: range [173184, 173185] (2 values)
NUMBER_OFFSET = 173186 # 0 to 500: range [173186, 173686] (501 values)
DIM_OFFSET = 173687 # 0 to 10: range [173687, 173697] (11 values)
HAS_C_A_OFFSET = 173698 # 0 to 1: range [173698, 173699] (2 values)
HAS_C_IX_OFFSET = 173700 # 0 to 1: range [173700, 173701] (2 values)
HAS_O_A_OFFSET = 173702 # 0 to 1: range [173702, 173703] (2 values)
HAS_O_IX_OFFSET = 173704 # 0 to 1: range [173704, 173705] (2 values)
FILL_RULE_OFFSET = 173706 # 0 to 4: range [173706, 173710] (5 values)
TYPE_OFFSET = 173711 # 0 to 40: range [173711, 173751] (41 values)
TEXT_RANGE_UNITS_OFFSET = 173752 # 0 to 10: range [173752, 173762] (11 values)
INV_OFFSET = 173763 # 0 to 1: range [173763, 173764] (2 values)
MODE_OFFSET = 173765 # 0 to 10: range [173765, 173775] (11 values)
TEXT_SHAPE_TYPE_OFFSET = 173776 # 0 to 10: range [173776, 173786] (11 values)
TEXT_RANDOM_OFFSET = 173787 # 0 to 1: range [173787, 173788] (2 values)
COLOR_POINTS_OFFSET = 173789 # 0 to 50: range [173789, 173839] (51 values)
ROUND_OFFSET = 173840 # -100 to 1000: range [173840, 174940] (1101 values)
RADIUS_OFFSET = 174941 # 0 to 300: range [174941, 175241] (301 values)
FREQUENCY_OFFSET = 175242 # 0 to 150: range [175242, 175392] (151 values)
SPEED_OFFSET = 175393 # -1000 to 1000: range [175393, 177393] (2001 values)
FONT_OFFSET = 177394 # -100 to 2000: range [177394, 179494] (2101 values)
COLOR_OFFSET = 179495 # 0 to 255: range [179495, 179750] (256 values)
LINE_CAP_OFFSET = 179752 # 1 to 3: range [179752, 179754] (3 values)
LINE_JOIN_OFFSET = 179757 # 1 to 3: range [179757, 179759] (3 values)
MITER_LIMIT_OFFSET = 179760 # 0 to 100: range [179760, 179860] (101 values)
EFFECT_OFFSET = 179861 # -250 to 1000: range [179861, 181111] (1251 values)
OPACITY_OFFSET = 181112 # 0 to 100: range [181112, 181212] (101 values)
WIDTH_VALUE_OFFSET = 181300 # 0 to 10000: range [181300, 191300] (10001 values for 0-100.00)
NO_OFFSET = 0
# Time dictionary parameters - all time-related values
time_params = {
LottieTensor.CMD_ANIMATION: [
LottieTensor.Index.Animation.IP,
LottieTensor.Index.Animation.OP
],
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.IN_POINT,
LottieTensor.Index.Layer.OUT_POINT,
LottieTensor.Index.Layer.START_TIME
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.IN_POINT,
LottieTensor.Index.NullLayer.OUT_POINT,
LottieTensor.Index.NullLayer.START_TIME
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.IN_POINT,
LottieTensor.Index.PrecompLayer.OUT_POINT,
LottieTensor.Index.PrecompLayer.START_TIME
],
LottieTensor.CMD_TEXT_LAYER: [
LottieTensor.Index.TextLayer.IN_POINT,
LottieTensor.Index.TextLayer.OUT_POINT,
LottieTensor.Index.TextLayer.START_TIME
],
LottieTensor.CMD_SOLID_LAYER: [
LottieTensor.Index.SolidLayer.IN_POINT,
LottieTensor.Index.SolidLayer.OUT_POINT,
LottieTensor.Index.SolidLayer.START_TIME
],
LottieTensor.CMD_KEYFRAME: [
LottieTensor.Index.Keyframe.T
],
LottieTensor.CMD_WIDTH_KEYFRAME: [
LottieTensor.Index.WidthKeyframe.T
],
LottieTensor.CMD_COLOR_KEYFRAME: [
LottieTensor.Index.Keyframe.T
],
LottieTensor.CMD_OPACITY_KEYFRAME: [
LottieTensor.Index.Keyframe.T
],
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.T
],
LottieTensor.CMD_MASK_PT_KEYFRAME: [
LottieTensor.Index.MaskPtKeyframe.T
],
LottieTensor.CMD_RANGE_START_KEYFRAME: [
LottieTensor.Index.RangeStartKeyframe.T
],
LottieTensor.CMD_RANGE_END_KEYFRAME: [
LottieTensor.Index.RangeEndKeyframe.T
],
LottieTensor.CMD_RANGE_OFFSET_KEYFRAME: [
LottieTensor.Index.RangeOffsetKeyframe.T
],
LottieTensor.CMD_DASH_KEYFRAME: [
LottieTensor.Index.DashKeyframe.T
],
}
# Space dictionary parameters - all spatial/positional values
space_params = {
LottieTensor.CMD_ANIMATION: [
LottieTensor.Index.Animation.W,
LottieTensor.Index.Animation.H
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.W,
LottieTensor.Index.PrecompLayer.H
],
LottieTensor.CMD_SOLID_LAYER: [
LottieTensor.Index.SolidLayer.WIDTH,
LottieTensor.Index.SolidLayer.HEIGHT
],
LottieTensor.CMD_DIMENSIONS: [
LottieTensor.Index.Dimensions.WIDTH,
LottieTensor.Index.Dimensions.HEIGHT
],
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.STROKE_WIDTH,
LottieTensor.Index.TextKeyframe.WRAP_POSITION_X,
LottieTensor.Index.TextKeyframe.WRAP_POSITION_Y,
LottieTensor.Index.TextKeyframe.WRAP_SIZE_X,
LottieTensor.Index.TextKeyframe.WRAP_SIZE_Y
],
LottieTensor.CMD_KEYFRAME: [
LottieTensor.Index.Keyframe.S1, LottieTensor.Index.Keyframe.S2, LottieTensor.Index.Keyframe.S3,
LottieTensor.Index.Keyframe.E1, LottieTensor.Index.Keyframe.E2, LottieTensor.Index.Keyframe.E3,
LottieTensor.Index.Keyframe.TO1, LottieTensor.Index.Keyframe.TO2, LottieTensor.Index.Keyframe.TO3,
LottieTensor.Index.Keyframe.TI1, LottieTensor.Index.Keyframe.TI2, LottieTensor.Index.Keyframe.TI3
],
#LottieTensor.CMD_WIDTH_KEYFRAME: [
# LottieTensor.Index.WidthKeyframe.S,
#],
LottieTensor.CMD_OPACITY_KEYFRAME: [
LottieTensor.Index.Keyframe.S1,
],
LottieTensor.CMD_POSITION: [
LottieTensor.Index.Transform.X, LottieTensor.Index.Transform.Y, LottieTensor.Index.Transform.Z,
LottieTensor.Index.TwoValues.VALUE1, LottieTensor.Index.TwoValues.VALUE2
],
LottieTensor.CMD_POSITION_X: [
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_POSITION_Y: [
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_POSITION_Z: [
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_POINT: [
LottieTensor.Index.Point.X, LottieTensor.Index.Point.Y,
LottieTensor.Index.Point.IN_X, LottieTensor.Index.Point.IN_Y,
LottieTensor.Index.Point.OUT_X, LottieTensor.Index.Point.OUT_Y
],
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.POSITION_X, LottieTensor.Index.TransformShape.POSITION_Y,
],
LottieTensor.CMD_SIZE: [
LottieTensor.Index.Transform.X, LottieTensor.Index.Transform.Y # 修改
],
LottieTensor.CMD_RECT_SIZE: [
LottieTensor.Index.Transform.X, LottieTensor.Index.Transform.Y # 修改
],
LottieTensor.CMD_ELLIPSE_SIZE: [
LottieTensor.Index.Transform.X, LottieTensor.Index.Transform.Y # 修改
],
LottieTensor.CMD_START_POINT: [
LottieTensor.Index.TwoValues.VALUE1, LottieTensor.Index.TwoValues.VALUE2
],
LottieTensor.CMD_END_POINT: [
LottieTensor.Index.TwoValues.VALUE1, LottieTensor.Index.TwoValues.VALUE2
],
LottieTensor.CMD_POINTS_STAR: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_START: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_END: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_OFFSET: [
LottieTensor.Index.SingleValue.VALUE
],
#LottieTensor.CMD_WIDTH: [
# LottieTensor.Index.SingleValue.VALUE
#],
#LottieTensor.CMD_DASH: [
# LottieTensor.Index.Dash.LENGTH
#],
#LottieTensor.CMD_DASH_OFFSET: [
# LottieTensor.Index.DashOffset.O
#],
#LottieTensor.CMD_DASH_KEYFRAME: [
# LottieTensor.Index.DashKeyframe.S,
# ],
LottieTensor.CMD_TR_POSITION: [
LottieTensor.Index.TrPosition.X, LottieTensor.Index.TrPosition.Y
],
LottieTensor.CMD_REPEATER_OFFSET: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_RANGE_START_KEYFRAME: [
LottieTensor.Index.RangeStartKeyframe.S,
],
LottieTensor.CMD_RANGE_END_KEYFRAME: [
LottieTensor.Index.RangeEndKeyframe.S,
],
LottieTensor.CMD_RANGE_OFFSET_KEYFRAME: [
LottieTensor.Index.RangeOffsetKeyframe.S,
],
LottieTensor.CMD_POSITION_ANIMATORS: [
LottieTensor.Index.PositionAnimators.K_X,
LottieTensor.Index.PositionAnimators.K_Y,
LottieTensor.Index.PositionAnimators.K_Z
],
LottieTensor.CMD_MASK_X: [
LottieTensor.Index.MaskX.K
],
LottieTensor.CMD_MASK_PT_K_I: list(range(20)),
LottieTensor.CMD_MASK_PT_K_O: list(range(20)),
LottieTensor.CMD_MASK_PT_K_V: list(range(20)),
LottieTensor.CMD_MASK_PT_KF_I: [
LottieTensor.Index.MaskPtKfI.X, LottieTensor.Index.MaskPtKfI.Y
],
LottieTensor.CMD_MASK_PT_KF_O: [
LottieTensor.Index.MaskPtKfO.X, LottieTensor.Index.MaskPtKfO.Y
],
LottieTensor.CMD_MASK_PT_KF_SHAPE_I: list(range(20)),
LottieTensor.CMD_MASK_PT_KF_SHAPE_O: list(range(20)),
LottieTensor.CMD_MASK_PT_KF_SHAPE_V: list(range(20)),
LottieTensor.CMD_VALUE: [
LottieTensor.Index.Value.VALUE
],
LottieTensor.CMD_MORE_OPTIONS: [
LottieTensor.Index.MoreOptions.ALIGNMENT_K1,
LottieTensor.Index.MoreOptions.ALIGNMENT_K2
],
LottieTensor.CMD_ALIGNMENT_K: [
LottieTensor.Index.AlignmentK.VALUE1,
LottieTensor.Index.AlignmentK.VALUE2
],
LottieTensor.CMD_CHAR: [
LottieTensor.Index.Char.W
],
LottieTensor.CMD_GRADIENT_FILL: [
LottieTensor.Index.GradientFill.START_POINT_X,
LottieTensor.Index.GradientFill.START_POINT_Y,
LottieTensor.Index.GradientFill.END_POINT_X,
LottieTensor.Index.GradientFill.END_POINT_Y,
LottieTensor.Index.GradientFill.HIGHLIGHT_LENGTH,
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.WIDTH,
LottieTensor.Index.GradientStroke.START_POINT_X,
LottieTensor.Index.GradientStroke.START_POINT_Y,
LottieTensor.Index.GradientStroke.END_POINT_X,
LottieTensor.Index.GradientStroke.END_POINT_Y,
LottieTensor.Index.GradientStroke.HIGHLIGHT_LENGTH,
],
LottieTensor.CMD_HIGHLIGHT_LENGTH: [
LottieTensor.Index.SingleValue.VALUE
],
}
width_value_params = {
LottieTensor.CMD_WIDTH: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_DASH: [
LottieTensor.Index.Dash.LENGTH
],
LottieTensor.CMD_DASH_OFFSET: [
LottieTensor.Index.DashOffset.O
],
LottieTensor.CMD_WIDTH_KEYFRAME: [
LottieTensor.Index.WidthKeyframe.S,
],
LottieTensor.CMD_DASH_KEYFRAME: [
LottieTensor.Index.DashKeyframe.S,
],
}
amplitude_params = {
LottieTensor.CMD_AMPLITUDE: [
LottieTensor.Index.Amplitude.VALUE
],
}
anchor_params = {
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.ANCHOR_X, LottieTensor.Index.TransformShape.ANCHOR_Y,
],
LottieTensor.CMD_TR_ANCHOR: [
LottieTensor.Index.TrAnchor.X, LottieTensor.Index.TrAnchor.Y
],
LottieTensor.CMD_ANCHOR: [
LottieTensor.Index.Transform.X, LottieTensor.Index.Transform.Y, LottieTensor.Index.Transform.Z
],
}
animated_params = {
LottieTensor.CMD_MORE_OPTIONS: [
LottieTensor.Index.MoreOptions.ALIGNMENT_A,
],
LottieTensor.CMD_ELLIPSE_SIZE: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_RECT_SIZE: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_POSITION: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_POSITION_X: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_POSITION_Y: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_POSITION_Z: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_SCALE: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_ROTATION: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_OPACITY: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_ANCHOR: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_SIZE: [
LottieTensor.Index.Transform.ANIMATED
],
LottieTensor.CMD_PATH: [
LottieTensor.Index.Path.ANIMATED,
],
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.COLOR_ANIMATED,
LottieTensor.Index.Fill.OPACITY_ANIMATED,
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.WIDTH_ANIMATED,
LottieTensor.Index.Stroke.COLOR_ANIMATED
],
LottieTensor.CMD_RECT_ROUNDED: [
LottieTensor.Index.SingleValue.ANIMATED
],
LottieTensor.CMD_START: [
LottieTensor.Index.SingleValue.ANIMATED
],
LottieTensor.CMD_END: [
LottieTensor.Index.SingleValue.ANIMATED
],
LottieTensor.CMD_OFFSET: [
LottieTensor.Index.SingleValue.ANIMATED
],
LottieTensor.CMD_MASK_PT: [
LottieTensor.Index.MaskPt.A,
],
LottieTensor.CMD_MASK_O: [
LottieTensor.Index.MaskO.A,
],
LottieTensor.CMD_MASK_X: [
LottieTensor.Index.MaskX.A,
],
LottieTensor.CMD_TM: [
LottieTensor.Index.Tm.A
],
LottieTensor.CMD_RANGE_START: [
LottieTensor.Index.RangeStart.A
],
LottieTensor.CMD_RANGE_END: [
LottieTensor.Index.RangeEnd.A
],
LottieTensor.CMD_RANGE_OFFSET: [
LottieTensor.Index.Amount.A,
],
LottieTensor.CMD_AMOUNT: [
LottieTensor.Index.Amount.A,
],
LottieTensor.CMD_MAX_EASE: [
LottieTensor.Index.MaxEase.A,
],
LottieTensor.CMD_MIN_EASE: [
LottieTensor.Index.MinEase.A,
],
LottieTensor.CMD_S_M: [
LottieTensor.Index.SM.A,
],
LottieTensor.CMD_OPACITY_ANIMATORS: [
LottieTensor.Index.OpacityAnimators.A,
],
LottieTensor.CMD_POSITION_ANIMATORS: [
LottieTensor.Index.PositionAnimators.A,
],
LottieTensor.CMD_SCALE_ANIMATORS: [
LottieTensor.Index.ScaleAnimators.A,
],
LottieTensor.CMD_ROTATION_ANIMATORS: [
LottieTensor.Index.RotationAnimators.A,
],
LottieTensor.CMD_TRACKING_ANIMATORS: [
LottieTensor.Index.TrackingAnimators.A,
],
LottieTensor.CMD_ALIGNMENT: [
LottieTensor.Index.Alignment.A
],
}
h_flag_params = {
LottieTensor.CMD_KEYFRAME: [
LottieTensor.Index.Keyframe.H_FLAG
],
}
offset_val_params = {
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.OFFSET,
],
}
ca_params = {
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.CA,
],
LottieTensor.CMD_CA: [
LottieTensor.Index.Ca.VALUE
],
}
justify_params = {
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.JUSTIFY,
],
LottieTensor.CMD_JUSTIFY: [
LottieTensor.Index.Justify.VALUE
],
}
text_tracking_params = {
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.TRACKING,
],
LottieTensor.CMD_TRACKING: [
LottieTensor.Index.Tracking.VALUE
],
}
has_stroke_color_params = {
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.HAS_STROKE_COLOR,
],
}
ix_params = {
LottieTensor.CMD_PATH: [
LottieTensor.Index.Path.IX,
LottieTensor.Index.Path.KS_IX,
],
LottieTensor.CMD_GROUP: [
LottieTensor.Index.Group.IX,
LottieTensor.Index.Group.CIX,
],
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.C_IX,
LottieTensor.Index.Fill.O_IX,
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.C_IX,
],
LottieTensor.CMD_RECT: [
LottieTensor.Index.Rect.IX,
],
LottieTensor.CMD_ROUNDED: [
LottieTensor.Index.SingleValue.IX
],
LottieTensor.CMD_TRIM: [
LottieTensor.Index.Trim.IX
],
LottieTensor.CMD_REPEATER: [
LottieTensor.Index.Repeater.IX
],
LottieTensor.CMD_COPIES: [
LottieTensor.Index.SingleValue.IX
],
LottieTensor.CMD_REPEATER_OFFSET: [
LottieTensor.Index.SingleValue.IX
],
LottieTensor.CMD_TR_P_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_A_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_S_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_R_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_SO_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_EO_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.ML2_IX,
],
LottieTensor.CMD_MASK_PT: [
LottieTensor.Index.MaskPt.IX,
],
LottieTensor.CMD_MASK_O: [
LottieTensor.Index.MaskO.IX,
],
LottieTensor.CMD_MASK_X: [
LottieTensor.Index.MaskX.IX,
],
LottieTensor.CMD_ZIG_ZAG: [
LottieTensor.Index.ZigZag.IX
],
LottieTensor.CMD_RANGE_OFFSET: [
LottieTensor.Index.Amount.IX
],
LottieTensor.CMD_AMOUNT: [
LottieTensor.Index.Amount.IX
],
LottieTensor.CMD_MAX_EASE: [
LottieTensor.Index.MaxEase.IX
],
LottieTensor.CMD_MIN_EASE: [
LottieTensor.Index.MinEase.IX
],
LottieTensor.CMD_S_M: [
LottieTensor.Index.SM.IX
],
LottieTensor.CMD_OPACITY_ANIMATORS: [
LottieTensor.Index.OpacityAnimators.IX
],
LottieTensor.CMD_POSITION_ANIMATORS: [
LottieTensor.Index.PositionAnimators.IX
],
LottieTensor.CMD_SCALE_ANIMATORS: [
LottieTensor.Index.ScaleAnimators.IX
],
LottieTensor.CMD_ROTATION_ANIMATORS: [
LottieTensor.Index.RotationAnimators.IX
],
LottieTensor.CMD_TRACKING_ANIMATORS: [
LottieTensor.Index.TrackingAnimators.IX
],
LottieTensor.CMD_ALIGNMENT_IX: [
LottieTensor.Index.AlignmentIx.VALUE
],
LottieTensor.CMD_MORE_OPTIONS: [
LottieTensor.Index.MoreOptions.ALIGNMENT_IX
],
LottieTensor.CMD_DASH: [
LottieTensor.Index.Dash.V_IX
],
LottieTensor.CMD_DASH_ANIMATED: [
LottieTensor.Index.DashAnimated.V_IX,
],
}
bm_params = {
LottieTensor.CMD_GROUP: [
LottieTensor.Index.Group.BM,
],
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.BM,
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.BM,
],
}
closed_params = {
LottieTensor.CMD_PATH: [
LottieTensor.Index.Path.CLOSED,
],
LottieTensor.CMD_BEZIER: [
LottieTensor.Index.Bezier.CLOSED
],
LottieTensor.CMD_MASK_PT_K_C: [
LottieTensor.Index.MaskPtK.C
],
LottieTensor.CMD_MASK_PT_KF_SHAPE: [
LottieTensor.Index.MaskPtKfShape.C,
],
}
direction_params = {
LottieTensor.CMD_RECT: [
LottieTensor.Index.Rect.D,
],
LottieTensor.CMD_STAR: [
LottieTensor.Index.Star.D,
],
}
star_type_params = {
LottieTensor.CMD_STAR: [
LottieTensor.Index.Star.SY
],
}
multiple_params = {
LottieTensor.CMD_MULTIPLE: [
LottieTensor.Index.SingleValue.VALUE
],
}
composite_params = {
LottieTensor.CMD_COMPOSITE: [
LottieTensor.Index.SingleValue.VALUE
],
}
skew_params = {
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.SKEW,
],
LottieTensor.CMD_SKEW: [
LottieTensor.Index.SingleValue.VALUE
],
}
skew_axis_params = {
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.SKEW_AXIS,
],
LottieTensor.CMD_SKEW_AXIS: [
LottieTensor.Index.SingleValue.VALUE
],
}
scale_params = {
LottieTensor.CMD_SCALE: [
LottieTensor.Index.Transform.X, LottieTensor.Index.Transform.Y, LottieTensor.Index.Transform.Z
],
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.SCALE_X, LottieTensor.Index.TransformShape.SCALE_Y,
],
LottieTensor.CMD_TR_SCALE: [
LottieTensor.Index.TwoValues.VALUE1, LottieTensor.Index.TwoValues.VALUE2
],
LottieTensor.CMD_SCALE_ANIMATORS: [
LottieTensor.Index.ScaleAnimators.K_X,
LottieTensor.Index.ScaleAnimators.K_Y,
LottieTensor.Index.ScaleAnimators.K_Z
],
}
rotation_params = {
LottieTensor.CMD_ROTATION: [
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.ROTATION
],
LottieTensor.CMD_STAR_ROTATION: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_ROTATION: [
LottieTensor.Index.TrRotation.VALUE
],
LottieTensor.CMD_ROTATION_ANIMATORS: [
LottieTensor.Index.RotationAnimators.K
],
LottieTensor.CMD_GRADIENT_FILL: [
LottieTensor.Index.GradientFill.HIGHLIGHT_ANGLE,
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.HIGHLIGHT_ANGLE
],
LottieTensor.CMD_HIGHLIGHT_ANGLE: [
LottieTensor.Index.SingleValue.VALUE
],
}
ease_params = {
LottieTensor.CMD_MAX_EASE: [
LottieTensor.Index.MaxEase.K
],
LottieTensor.CMD_MIN_EASE: [
LottieTensor.Index.MinEase.K
],
}
smooth_params = {
LottieTensor.CMD_S_M: [
LottieTensor.Index.SM.K
],
}
tracking_params = {
LottieTensor.CMD_TRACKING_ANIMATORS: [
LottieTensor.Index.TrackingAnimators.K
],
}
index_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.INDEX,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.INDEX,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.INDEX,
],
LottieTensor.CMD_TEXT_LAYER: [
LottieTensor.Index.TextLayer.INDEX,
],
LottieTensor.CMD_SOLID_LAYER: [
LottieTensor.Index.SolidLayer.INDEX,
],
LottieTensor.CMD_PARENT: [
LottieTensor.Index.Parent.PARENT_INDEX
],
LottieTensor.CMD_PATH: [
LottieTensor.Index.Path.IND,
],
LottieTensor.CMD_COLOR: [
LottieTensor.Index.Color.INDEX
],
LottieTensor.CMD_MASK: [
LottieTensor.Index.Mask.INDEX,
],
LottieTensor.CMD_MASK_PT_KEYFRAME: [
LottieTensor.Index.MaskPtKeyframe.INDEX
],
LottieTensor.CMD_MASK_PT_KF_SHAPE: [
LottieTensor.Index.MaskPtKfShape.INDEX,
],
LottieTensor.CMD_EFFECT: [
LottieTensor.Index.Effect.INDEX,
],
LottieTensor.CMD_LAYER_EFFECT: [
LottieTensor.Index.LayerEffect.INDEX,
],
LottieTensor.CMD_DROPDOWN: [
LottieTensor.Index.Dropdown.INDEX,
],
LottieTensor.CMD_NO_VALUE: [
LottieTensor.Index.NO_VALUE.INDEX,
],
LottieTensor.CMD_IGNORED: [
LottieTensor.Index.Ignored.INDEX,
],
LottieTensor.CMD_SLIDER: [
LottieTensor.Index.Slider.INDEX,
],
}
ddd_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.DDD,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.DDD,
],
LottieTensor.CMD_ANIMATION: [
LottieTensor.Index.Animation.DDD
],
}
hd_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.HD,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.HD,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.HD,
],
LottieTensor.CMD_PATH: [
LottieTensor.Index.Path.HD,
],
LottieTensor.CMD_GROUP: [
LottieTensor.Index.Group.HD,
],
LottieTensor.CMD_RECT: [
LottieTensor.Index.Rect.HD,
],
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.HD
],
}
cp_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.CP,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.CP,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.CP,
],
}
has_mask_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.HAS_MASK,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.HAS_MASK,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.HAS_MASK,
],
LottieTensor.CMD_TEXT_LAYER: [
LottieTensor.Index.TextLayer.HAS_MASK,
],
LottieTensor.CMD_SOLID_LAYER: [
LottieTensor.Index.SolidLayer.HAS_MASK
],
}
ao_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.AO,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.AO,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.AO,
],
}
tt_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.TT,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.TT,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.TT,
],
}
tp_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.TP,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.TP,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.TP,
],
}
td_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.TD,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.TD,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.TD,
],
}
ct_params = {
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.CT,
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.CT,
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.CT,
],
}
number_params = {
LottieTensor.CMD_COPIES: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_AMOUNT: [
LottieTensor.Index.Amount.K
],
LottieTensor.CMD_RANGE_OFFSET: [
LottieTensor.Index.Amount.K
],
LottieTensor.CMD_GROUP: [
LottieTensor.Index.Group.NP
],
LottieTensor.CMD_EFFECT: [
LottieTensor.Index.Effect.NP,
],
}
dim_params = {
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.COLOR_DIM,
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.COLOR_DIM,
],
}
has_c_a_params = {
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.HAS_C_A,
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.HAS_C_A,
],
}
has_c_ix_params = {
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.HAS_C_IX,
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.HAS_C_IX,
],
}
has_o_a_params = {
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.HAS_O_A,
],
}
has_o_ix_params = {
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.HAS_O_IX,
],
}
fill_rule_params = {
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.FILL_RULE,
],
LottieTensor.CMD_GRADIENT_FILL: [
LottieTensor.Index.GradientFill.FILL_RULE,
],
LottieTensor.CMD_FILL_RULE: [
LottieTensor.Index.SingleValue.VALUE
],
}
type_params = {
LottieTensor.CMD_GRADIENT_FILL: [
LottieTensor.Index.GradientFill.GRADIENT_TYPE,
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.GRADIENT_TYPE,
],
LottieTensor.CMD_GRADIENT_TYPE: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_POINT_TYPE: [
LottieTensor.Index.PointType.VALUE
],
LottieTensor.CMD_RANGE_SELECTOR: [
LottieTensor.Index.RangeSelector.T,
],
LottieTensor.CMD_DASH: [
LottieTensor.Index.Dash.TYPE,
],
LottieTensor.CMD_DASH_ANIMATED: [
LottieTensor.Index.DashAnimated.TYPE,
],
LottieTensor.CMD_EFFECT: [
LottieTensor.Index.Effect.TYPE,
],
}
text_range_units = {
LottieTensor.CMD_RANGE_SELECTOR: [
LottieTensor.Index.RangeSelector.R,
],
}
inv_params = {
LottieTensor.CMD_MASK: [
LottieTensor.Index.Mask.INV,
],
}
mode_params = {
LottieTensor.CMD_MERGE_MODE: [
LottieTensor.Index.MergeMode.MODE
],
LottieTensor.CMD_MASK: [
LottieTensor.Index.Mask.MODE,
],
LottieTensor.CMD_RANGE_SELECTOR: [
LottieTensor.Index.RangeSelector.B,
],
}
text_shape_type = {
LottieTensor.CMD_RANGE_SELECTOR: [
LottieTensor.Index.RangeSelector.SH,
],
}
text_random = {
LottieTensor.CMD_RANGE_SELECTOR: [
LottieTensor.Index.RangeSelector.RN
],
}
color_points_params = {
LottieTensor.CMD_GRADIENT_FILL: [
LottieTensor.Index.GradientFill.COLOR_POINTS
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.COLOR_POINTS
],
LottieTensor.CMD_COLOR_POINTS: [
LottieTensor.Index.SingleValue.VALUE
],
}
round_params = {
LottieTensor.CMD_ROUNDED: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_RECT_ROUNDED: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_INNER_ROUNDNESS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_OUTER_ROUNDNESS: [
LottieTensor.Index.SingleValue.VALUE
],
}
radius_params = {
LottieTensor.CMD_INNER_RADIUS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_OUTER_RADIUS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_RADIUS: [
LottieTensor.Index.Radius.VALUE
],
}
frequency_params = {
LottieTensor.CMD_ANIMATION: [
LottieTensor.Index.Animation.FR,
],
LottieTensor.CMD_FREQUENCY: [
LottieTensor.Index.Frequency.VALUE
],
LottieTensor.CMD_ASSET: [
LottieTensor.Index.Asset.FR
],
}
speed_params = {
LottieTensor.CMD_KEYFRAME: [
LottieTensor.Index.Keyframe.I_X, LottieTensor.Index.Keyframe.I_Y,
LottieTensor.Index.Keyframe.O_X, LottieTensor.Index.Keyframe.O_Y,
LottieTensor.Index.Keyframe.I_X2, LottieTensor.Index.Keyframe.I_Y2,
LottieTensor.Index.Keyframe.O_X2, LottieTensor.Index.Keyframe.O_Y2,
LottieTensor.Index.Keyframe.I_X3, LottieTensor.Index.Keyframe.I_Y3,
LottieTensor.Index.Keyframe.O_X3, LottieTensor.Index.Keyframe.O_Y3,
],
LottieTensor.CMD_WIDTH_KEYFRAME: [
LottieTensor.Index.WidthKeyframe.I_X, LottieTensor.Index.WidthKeyframe.I_Y,
LottieTensor.Index.WidthKeyframe.O_X, LottieTensor.Index.WidthKeyframe.O_Y
],
LottieTensor.CMD_OPACITY_KEYFRAME: [
LottieTensor.Index.Keyframe.I_X, LottieTensor.Index.Keyframe.I_Y,
LottieTensor.Index.Keyframe.O_X, LottieTensor.Index.Keyframe.O_Y
],
LottieTensor.CMD_COLOR_KEYFRAME: [
LottieTensor.Index.Keyframe.I_X, LottieTensor.Index.Keyframe.I_Y,
LottieTensor.Index.Keyframe.O_X, LottieTensor.Index.Keyframe.O_Y
],
LottieTensor.CMD_DASH_KEYFRAME: [
LottieTensor.Index.DashKeyframe.I_X, LottieTensor.Index.DashKeyframe.I_Y,
LottieTensor.Index.DashKeyframe.O_X, LottieTensor.Index.DashKeyframe.O_Y
],
LottieTensor.CMD_RANGE_START_KEYFRAME: [
LottieTensor.Index.RangeStartKeyframe.I_X, LottieTensor.Index.RangeStartKeyframe.I_Y,
LottieTensor.Index.RangeStartKeyframe.O_X, LottieTensor.Index.RangeStartKeyframe.O_Y
],
LottieTensor.CMD_RANGE_END_KEYFRAME: [
LottieTensor.Index.RangeEndKeyframe.I_X, LottieTensor.Index.RangeEndKeyframe.I_Y,
LottieTensor.Index.RangeEndKeyframe.O_X, LottieTensor.Index.RangeEndKeyframe.O_Y
],
LottieTensor.CMD_RANGE_OFFSET_KEYFRAME: [
LottieTensor.Index.RangeOffsetKeyframe.I_X, LottieTensor.Index.RangeOffsetKeyframe.I_Y,
LottieTensor.Index.RangeOffsetKeyframe.O_X, LottieTensor.Index.RangeOffsetKeyframe.O_Y
],
}
font_params = {
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.FONT_SIZE,
LottieTensor.Index.TextKeyframe.LINE_HEIGHT,
LottieTensor.Index.TextKeyframe.LETTER_SPACING
],
LottieTensor.CMD_FONT_SIZE: [
LottieTensor.Index.FontSize.SIZE
],
LottieTensor.CMD_LINE_HEIGHT: [
LottieTensor.Index.LineHeight.VALUE
],
LottieTensor.CMD_LETTER_SPACING: [
LottieTensor.Index.LetterSpacing.VALUE
],
LottieTensor.CMD_FONT: [
LottieTensor.Index.Font.ASCENT
],
LottieTensor.CMD_CHAR: [
LottieTensor.Index.Char.SIZE
],
}
color_params = {
LottieTensor.CMD_TEXT_KEYFRAME: [
LottieTensor.Index.TextKeyframe.FILL_COLOR_R,
LottieTensor.Index.TextKeyframe.FILL_COLOR_G,
LottieTensor.Index.TextKeyframe.FILL_COLOR_B,
LottieTensor.Index.TextKeyframe.STROKE_COLOR_R,
LottieTensor.Index.TextKeyframe.STROKE_COLOR_G,
LottieTensor.Index.TextKeyframe.STROKE_COLOR_B
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.R,
LottieTensor.Index.Stroke.G,
LottieTensor.Index.Stroke.B,
LottieTensor.Index.Stroke.A
],
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.R,
LottieTensor.Index.Fill.G,
LottieTensor.Index.Fill.B,
],
LottieTensor.CMD_FILL_COLOR: [
LottieTensor.Index.FillColor.R,
LottieTensor.Index.FillColor.G,
LottieTensor.Index.FillColor.B
],
LottieTensor.CMD_SOLID_LAYER: [
LottieTensor.Index.SolidLayer.COLOR_R,
LottieTensor.Index.SolidLayer.COLOR_G,
LottieTensor.Index.SolidLayer.COLOR_B,
LottieTensor.Index.SolidLayer.COLOR_A
],
LottieTensor.CMD_COLOR: [
LottieTensor.Index.Color.R,
LottieTensor.Index.Color.G,
LottieTensor.Index.Color.B
],
LottieTensor.CMD_COLOR_KEYFRAME: [
LottieTensor.Index.Keyframe.S1,
LottieTensor.Index.Keyframe.S2,
LottieTensor.Index.Keyframe.S3,
LottieTensor.Index.Keyframe.E1
],
LottieTensor.CMD_ORIGINAL_COLORS: list(range(LottieTensor.Index.OriginalColors.COUNT)),
LottieTensor.CMD_GRADIENT_FILL: list(range(LottieTensor.Index.GradientFill.ORIGINAL_COLOR_0,
LottieTensor.Index.GradientFill.ORIGINAL_COLOR_23 + 1)),
LottieTensor.CMD_GRADIENT_STROKE: list(range(LottieTensor.Index.GradientStroke.ORIGINAL_COLOR_0,
LottieTensor.Index.GradientStroke.ORIGINAL_COLOR_23 + 1)),
}
line_cap_params = {
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.LC,
],
LottieTensor.CMD_LINE_CAP: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.LINE_CAP,
],
}
line_join_params = {
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.LJ,
],
LottieTensor.CMD_LINE_JOIN: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.LINE_JOIN,
],
}
miter_limit_params = {
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.ML
],
LottieTensor.CMD_MITER_LIMIT: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_ML2: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.MITER_LIMIT,
LottieTensor.Index.GradientStroke.ML2
],
}
enabled_params = {
LottieTensor.CMD_EFFECT: [
LottieTensor.Index.Effect.ENABLED
],
}
effect_params = {
LottieTensor.CMD_LAYER_EFFECT: [
LottieTensor.Index.LayerEffect.VALUE
],
LottieTensor.CMD_DROPDOWN: [
LottieTensor.Index.Dropdown.VALUE
],
LottieTensor.CMD_NO_VALUE: [
LottieTensor.Index.NO_VALUE.VALUE
],
LottieTensor.CMD_IGNORED: [
LottieTensor.Index.Ignored.VALUE
],
LottieTensor.CMD_SLIDER: [
LottieTensor.Index.Slider.VALUE
],
}
opacity_params = {
LottieTensor.CMD_GRADIENT_FILL: [
LottieTensor.Index.GradientFill.OPACITY
],
LottieTensor.CMD_GRADIENT_STROKE: [
LottieTensor.Index.GradientStroke.OPACITY
],
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.OPACITY
],
LottieTensor.CMD_OPACITY: [
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.OPACITY,
],
LottieTensor.CMD_TR_START_OPACITY: [
LottieTensor.Index.TrStartOpacity.VALUE
],
LottieTensor.CMD_TR_END_OPACITY: [
LottieTensor.Index.TrEndOpacity.VALUE
],
LottieTensor.CMD_OPACITY_ANIMATORS: [
LottieTensor.Index.OpacityAnimators.K
],
LottieTensor.CMD_MASK_O: [
LottieTensor.Index.MaskO.K
],
}
# Tokenizer parameters (no offset)
tokenizer_params = {
LottieTensor.CMD_TEXT_KEYFRAME: list(range(LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKENS_START,
LottieTensor.Index.TextKeyframe.TEXT_TOKEN_COUNT + 1)),
LottieTensor.CMD_ASSET: list(range(LottieTensor.Index.Asset.ID_TOKEN_0,
LottieTensor.Index.Asset.ID_TOKEN_COUNT + 1)),
LottieTensor.CMD_REFERENCE_ID: list(range(LottieTensor.Index.ReferenceId.ID_TOKEN_0,
LottieTensor.Index.ReferenceId.ID_TOKEN_COUNT + 1)),
LottieTensor.CMD_FONT: list(range(LottieTensor.Index.Font.FAMILY_TOKEN_0,
LottieTensor.Index.Font.STYLE_TOKEN_COUNT + 1)),
LottieTensor.CMD_CHAR: list(range(LottieTensor.Index.Char.CH_TOKEN_0,
LottieTensor.Index.Char.FAMILY_TOKEN_COUNT + 1)),
}
# 添加 width_value_params 的判断(在其他判断之前)
if cmd_idx in width_value_params and param_idx in width_value_params[cmd_idx]:
return WIDTH_VALUE_OFFSET
elif cmd_idx in tokenizer_params and param_idx in tokenizer_params[cmd_idx]:
return NO_OFFSET # No offset for tokenizer tokens
elif cmd_idx in time_params and param_idx in time_params[cmd_idx]:
return TIME_OFFSET
elif cmd_idx in space_params and param_idx in space_params[cmd_idx]:
return SPACE_OFFSET
elif cmd_idx in amplitude_params and param_idx in amplitude_params[cmd_idx]:
return AMPLITUDE_OFFSET
elif cmd_idx in anchor_params and param_idx in anchor_params[cmd_idx]:
return ANCHOR_OFFSET
elif cmd_idx in animated_params and param_idx in animated_params[cmd_idx]:
return ANIMATED_OFFSET
elif cmd_idx in h_flag_params and param_idx in h_flag_params[cmd_idx]:
return H_FLAG_OFFSET
elif cmd_idx in offset_val_params and param_idx in offset_val_params[cmd_idx]:
return OFFSET_VAL_OFFSET
elif cmd_idx in ca_params and param_idx in ca_params[cmd_idx]:
return CA_OFFSET
elif cmd_idx in justify_params and param_idx in justify_params[cmd_idx]:
return JUSTIFY_OFFSET
elif cmd_idx in text_tracking_params and param_idx in text_tracking_params[cmd_idx]:
return TEXT_TRACKING_OFFSET
elif cmd_idx in has_stroke_color_params and param_idx in has_stroke_color_params[cmd_idx]:
return HAS_STROKE_COLOR_OFFSET
elif cmd_idx in ix_params and param_idx in ix_params[cmd_idx]:
return IX_OFFSET
elif cmd_idx in bm_params and param_idx in bm_params[cmd_idx]:
return BM_OFFSET
elif cmd_idx in closed_params and param_idx in closed_params[cmd_idx]:
return CLOSED_OFFSET
elif cmd_idx in direction_params and param_idx in direction_params[cmd_idx]:
return DIRECTION_OFFSET
elif cmd_idx in star_type_params and param_idx in star_type_params[cmd_idx]:
return STAR_TYPE_OFFSET
elif cmd_idx in multiple_params and param_idx in multiple_params[cmd_idx]:
return MULTIPLE_OFFSET
elif cmd_idx in composite_params and param_idx in composite_params[cmd_idx]:
return COMPOSITE_OFFSET
elif cmd_idx in skew_params and param_idx in skew_params[cmd_idx]:
return SKEW_OFFSET
elif cmd_idx in skew_axis_params and param_idx in skew_axis_params[cmd_idx]:
return SKEW_AXIS_OFFSET
elif cmd_idx in scale_params and param_idx in scale_params[cmd_idx]:
return SCALE_OFFSET
elif cmd_idx in rotation_params and param_idx in rotation_params[cmd_idx]:
return ROTATION_OFFSET
elif cmd_idx in ease_params and param_idx in ease_params[cmd_idx]:
return EASE_OFFSET
elif cmd_idx in smooth_params and param_idx in smooth_params[cmd_idx]:
return SMOOTH_OFFSET
elif cmd_idx in tracking_params and param_idx in tracking_params[cmd_idx]:
return TRACKING_OFFSET
elif cmd_idx in index_params and param_idx in index_params[cmd_idx]:
return INDEX_OFFSET
elif cmd_idx in ddd_params and param_idx in ddd_params[cmd_idx]:
return DDD_OFFSET
elif cmd_idx in hd_params and param_idx in hd_params[cmd_idx]:
return HD_OFFSET
elif cmd_idx in cp_params and param_idx in cp_params[cmd_idx]:
return CP_OFFSET
elif cmd_idx in has_mask_params and param_idx in has_mask_params[cmd_idx]:
return HAS_MASK_OFFSET
elif cmd_idx in ao_params and param_idx in ao_params[cmd_idx]:
return AO_OFFSET
elif cmd_idx in tt_params and param_idx in tt_params[cmd_idx]:
return TT_OFFSET
elif cmd_idx in tp_params and param_idx in tp_params[cmd_idx]:
return TP_OFFSET
elif cmd_idx in td_params and param_idx in td_params[cmd_idx]:
return TD_OFFSET
elif cmd_idx in ct_params and param_idx in ct_params[cmd_idx]:
return CT_OFFSET
elif cmd_idx in number_params and param_idx in number_params[cmd_idx]:
return NUMBER_OFFSET
elif cmd_idx in dim_params and param_idx in dim_params[cmd_idx]:
return DIM_OFFSET
elif cmd_idx in has_c_a_params and param_idx in has_c_a_params[cmd_idx]:
return HAS_C_A_OFFSET
elif cmd_idx in has_c_ix_params and param_idx in has_c_ix_params[cmd_idx]:
return HAS_C_IX_OFFSET
elif cmd_idx in has_o_a_params and param_idx in has_o_a_params[cmd_idx]:
return HAS_O_A_OFFSET
elif cmd_idx in has_o_ix_params and param_idx in has_o_ix_params[cmd_idx]:
return HAS_O_IX_OFFSET
elif cmd_idx in fill_rule_params and param_idx in fill_rule_params[cmd_idx]:
return FILL_RULE_OFFSET
elif cmd_idx in type_params and param_idx in type_params[cmd_idx]:
return TYPE_OFFSET
elif cmd_idx in text_range_units and param_idx in text_range_units[cmd_idx]:
return TEXT_RANGE_UNITS_OFFSET
elif cmd_idx in inv_params and param_idx in inv_params[cmd_idx]:
return INV_OFFSET
elif cmd_idx in mode_params and param_idx in mode_params[cmd_idx]:
return MODE_OFFSET
elif cmd_idx in text_shape_type and param_idx in text_shape_type[cmd_idx]:
return TEXT_SHAPE_TYPE_OFFSET
elif cmd_idx in text_random and param_idx in text_random[cmd_idx]:
return TEXT_RANDOM_OFFSET
elif cmd_idx in color_points_params and param_idx in color_points_params[cmd_idx]:
return COLOR_POINTS_OFFSET
elif cmd_idx in round_params and param_idx in round_params[cmd_idx]:
return ROUND_OFFSET
elif cmd_idx in radius_params and param_idx in radius_params[cmd_idx]:
return RADIUS_OFFSET
elif cmd_idx in frequency_params and param_idx in frequency_params[cmd_idx]:
return FREQUENCY_OFFSET
elif cmd_idx in speed_params and param_idx in speed_params[cmd_idx]:
return SPEED_OFFSET
elif cmd_idx in font_params and param_idx in font_params[cmd_idx]:
return FONT_OFFSET
elif cmd_idx in color_params and param_idx in color_params[cmd_idx]:
return COLOR_OFFSET
elif cmd_idx in line_cap_params and param_idx in line_cap_params[cmd_idx]:
return LINE_CAP_OFFSET
elif cmd_idx in line_join_params and param_idx in line_join_params[cmd_idx]:
return LINE_JOIN_OFFSET
elif cmd_idx in miter_limit_params and param_idx in miter_limit_params[cmd_idx]:
return MITER_LIMIT_OFFSET
elif cmd_idx in effect_params and param_idx in effect_params[cmd_idx]:
return EFFECT_OFFSET
elif cmd_idx in opacity_params and param_idx in opacity_params[cmd_idx]:
return OPACITY_OFFSET
else:
return 0 # Default to no offset if not found
LottieTensor._OFFSET_CACHE[cache_key] = offset
return offset
@staticmethod
def get_command_param_indices(cmd_idx: int) -> List[int]:
"""
Get the list of parameter indices for a command in their fixed order.
Returns empty list for commands without parameters.
"""
param_orders = {
LottieTensor.CMD_ANIMATION: [
LottieTensor.Index.Animation.FR,
LottieTensor.Index.Animation.IP,
LottieTensor.Index.Animation.OP,
LottieTensor.Index.Animation.W,
LottieTensor.Index.Animation.H,
LottieTensor.Index.Animation.DDD
],
LottieTensor.CMD_LAYER: [
LottieTensor.Index.Layer.INDEX,
LottieTensor.Index.Layer.IN_POINT,
LottieTensor.Index.Layer.OUT_POINT,
LottieTensor.Index.Layer.START_TIME,
LottieTensor.Index.Layer.DDD,
LottieTensor.Index.Layer.HD,
LottieTensor.Index.Layer.CP,
LottieTensor.Index.Layer.HAS_MASK,
LottieTensor.Index.Layer.AO,
LottieTensor.Index.Layer.TT,
LottieTensor.Index.Layer.TP,
LottieTensor.Index.Layer.TD,
LottieTensor.Index.Layer.CT
],
LottieTensor.CMD_NULL_LAYER: [
LottieTensor.Index.NullLayer.INDEX,
LottieTensor.Index.NullLayer.IN_POINT,
LottieTensor.Index.NullLayer.OUT_POINT,
LottieTensor.Index.NullLayer.START_TIME,
LottieTensor.Index.NullLayer.CT,
LottieTensor.Index.NullLayer.HD,
LottieTensor.Index.NullLayer.HAS_MASK,
LottieTensor.Index.NullLayer.AO,
LottieTensor.Index.NullLayer.TT,
LottieTensor.Index.NullLayer.TP,
LottieTensor.Index.NullLayer.TD,
LottieTensor.Index.NullLayer.CP
],
LottieTensor.CMD_PRECOMP_LAYER: [
LottieTensor.Index.PrecompLayer.INDEX,
LottieTensor.Index.PrecompLayer.IN_POINT,
LottieTensor.Index.PrecompLayer.OUT_POINT,
LottieTensor.Index.PrecompLayer.START_TIME,
LottieTensor.Index.PrecompLayer.W,
LottieTensor.Index.PrecompLayer.H,
LottieTensor.Index.PrecompLayer.CT,
LottieTensor.Index.PrecompLayer.HAS_MASK,
LottieTensor.Index.PrecompLayer.AO,
LottieTensor.Index.PrecompLayer.TT,
LottieTensor.Index.PrecompLayer.TP,
LottieTensor.Index.PrecompLayer.TD,
LottieTensor.Index.PrecompLayer.DDD,
LottieTensor.Index.PrecompLayer.HD,
LottieTensor.Index.PrecompLayer.CP
],
LottieTensor.CMD_TEXT_LAYER: [
LottieTensor.Index.TextLayer.INDEX,
LottieTensor.Index.TextLayer.IN_POINT,
LottieTensor.Index.TextLayer.OUT_POINT,
LottieTensor.Index.TextLayer.START_TIME,
LottieTensor.Index.TextLayer.HAS_MASK
],
LottieTensor.CMD_SOLID_LAYER: [
LottieTensor.Index.SolidLayer.INDEX,
LottieTensor.Index.SolidLayer.IN_POINT,
LottieTensor.Index.SolidLayer.OUT_POINT,
LottieTensor.Index.SolidLayer.START_TIME,
LottieTensor.Index.SolidLayer.WIDTH,
LottieTensor.Index.SolidLayer.HEIGHT,
LottieTensor.Index.SolidLayer.HAS_MASK,
LottieTensor.Index.SolidLayer.COLOR_R,
LottieTensor.Index.SolidLayer.COLOR_G,
LottieTensor.Index.SolidLayer.COLOR_B,
LottieTensor.Index.SolidLayer.COLOR_A
],
LottieTensor.CMD_TRANSFORM: [],
LottieTensor.CMD_POSITION: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X,
LottieTensor.Index.Transform.Y,
LottieTensor.Index.Transform.Z
],
LottieTensor.CMD_POSITION_X: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_POSITION_Y: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_POSITION_Z: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_SCALE: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X,
LottieTensor.Index.Transform.Y,
LottieTensor.Index.Transform.Z
],
LottieTensor.CMD_ROTATION: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_OPACITY: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X
],
LottieTensor.CMD_ANCHOR: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X,
LottieTensor.Index.Transform.Y,
LottieTensor.Index.Transform.Z
],
LottieTensor.CMD_KEYFRAME: [
LottieTensor.Index.Keyframe.T,
LottieTensor.Index.Keyframe.S1,
LottieTensor.Index.Keyframe.S2,
LottieTensor.Index.Keyframe.S3,
LottieTensor.Index.Keyframe.I_X,
LottieTensor.Index.Keyframe.I_Y,
LottieTensor.Index.Keyframe.O_X,
LottieTensor.Index.Keyframe.O_Y,
LottieTensor.Index.Keyframe.TO1,
LottieTensor.Index.Keyframe.TO2,
LottieTensor.Index.Keyframe.TO3,
LottieTensor.Index.Keyframe.TI1,
LottieTensor.Index.Keyframe.TI2,
LottieTensor.Index.Keyframe.TI3,
LottieTensor.Index.Keyframe.I_X2,
LottieTensor.Index.Keyframe.I_X3,
LottieTensor.Index.Keyframe.I_Y2,
LottieTensor.Index.Keyframe.I_Y3,
LottieTensor.Index.Keyframe.O_X2,
LottieTensor.Index.Keyframe.O_X3,
LottieTensor.Index.Keyframe.O_Y2,
LottieTensor.Index.Keyframe.O_Y3,
LottieTensor.Index.Keyframe.H_FLAG,
LottieTensor.Index.Keyframe.E1,
LottieTensor.Index.Keyframe.E2,
LottieTensor.Index.Keyframe.E3
],
LottieTensor.CMD_GROUP: [
LottieTensor.Index.Group.IX,
LottieTensor.Index.Group.CIX,
LottieTensor.Index.Group.BM,
LottieTensor.Index.Group.HD,
LottieTensor.Index.Group.NP
],
LottieTensor.CMD_PATH: [
LottieTensor.Index.Path.IX,
LottieTensor.Index.Path.IND,
LottieTensor.Index.Path.KS_IX,
LottieTensor.Index.Path.CLOSED,
LottieTensor.Index.Path.HD,
LottieTensor.Index.Path.ANIMATED
],
LottieTensor.CMD_POINT: [
LottieTensor.Index.Point.X,
LottieTensor.Index.Point.Y,
LottieTensor.Index.Point.IN_X,
LottieTensor.Index.Point.IN_Y,
LottieTensor.Index.Point.OUT_X,
LottieTensor.Index.Point.OUT_Y
],
LottieTensor.CMD_FILL: [
LottieTensor.Index.Fill.R,
LottieTensor.Index.Fill.G,
LottieTensor.Index.Fill.B,
LottieTensor.Index.Fill.COLOR_DIM,
LottieTensor.Index.Fill.HAS_C_A,
LottieTensor.Index.Fill.HAS_C_IX,
LottieTensor.Index.Fill.C_IX,
LottieTensor.Index.Fill.BM,
LottieTensor.Index.Fill.FILL_RULE,
LottieTensor.Index.Fill.OPACITY,
LottieTensor.Index.Fill.COLOR_ANIMATED,
LottieTensor.Index.Fill.OPACITY_ANIMATED,
LottieTensor.Index.Fill.HAS_O_A,
LottieTensor.Index.Fill.HAS_O_IX,
LottieTensor.Index.Fill.O_IX
],
LottieTensor.CMD_STROKE: [
LottieTensor.Index.Stroke.R,
LottieTensor.Index.Stroke.G,
LottieTensor.Index.Stroke.B,
LottieTensor.Index.Stroke.COLOR_DIM,
LottieTensor.Index.Stroke.HAS_C_A,
LottieTensor.Index.Stroke.HAS_C_IX,
LottieTensor.Index.Stroke.C_IX,
LottieTensor.Index.Stroke.BM,
LottieTensor.Index.Stroke.LC,
LottieTensor.Index.Stroke.LJ,
LottieTensor.Index.Stroke.ML,
LottieTensor.Index.Stroke.WIDTH_ANIMATED,
LottieTensor.Index.Stroke.COLOR_ANIMATED,
LottieTensor.Index.Stroke.A
],
LottieTensor.CMD_TRANSFORM_SHAPE: [
LottieTensor.Index.TransformShape.POSITION_X,
LottieTensor.Index.TransformShape.POSITION_Y,
LottieTensor.Index.TransformShape.SCALE_X,
LottieTensor.Index.TransformShape.SCALE_Y,
LottieTensor.Index.TransformShape.ROTATION,
LottieTensor.Index.TransformShape.OPACITY,
LottieTensor.Index.TransformShape.ANCHOR_X,
LottieTensor.Index.TransformShape.ANCHOR_Y,
LottieTensor.Index.TransformShape.SKEW,
LottieTensor.Index.TransformShape.SKEW_AXIS,
LottieTensor.Index.TransformShape.HD
],
LottieTensor.CMD_RECT: [
LottieTensor.Index.Rect.HD,
LottieTensor.Index.Rect.D
],
LottieTensor.CMD_ELLIPSE: [],
LottieTensor.CMD_BEZIER: [
LottieTensor.Index.Bezier.CLOSED
],
LottieTensor.CMD_SIZE: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X, # 修改:从 TwoValues.VALUE1 改为 Transform.X
LottieTensor.Index.Transform.Y # 修改:从 TwoValues.VALUE2 改为 Transform.Y
],
LottieTensor.CMD_RECT_SIZE: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X, # 修改
LottieTensor.Index.Transform.Y # 修改
],
LottieTensor.CMD_ELLIPSE_SIZE: [
LottieTensor.Index.Transform.ANIMATED,
LottieTensor.Index.Transform.X, # 修改
LottieTensor.Index.Transform.Y # 修改
],
LottieTensor.CMD_ROUNDED: [
LottieTensor.Index.SingleValue.VALUE,
LottieTensor.Index.SingleValue.IX
],
LottieTensor.CMD_RECT_ROUNDED: [
LottieTensor.Index.SingleValue.ANIMATED,
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TRIM: [
LottieTensor.Index.Trim.IX
],
LottieTensor.CMD_START: [
LottieTensor.Index.SingleValue.ANIMATED,
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_END: [
LottieTensor.Index.SingleValue.ANIMATED,
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_OFFSET: [
LottieTensor.Index.SingleValue.ANIMATED,
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_PARENT: [
LottieTensor.Index.Parent.PARENT_INDEX
],
LottieTensor.CMD_REFERENCE_ID: list(range(11)), # 11 tokens
LottieTensor.CMD_DIMENSIONS: [
LottieTensor.Index.Dimensions.WIDTH,
LottieTensor.Index.Dimensions.HEIGHT
],
LottieTensor.CMD_ASSET: list(range(12)), # FR + 10 tokens + count
LottieTensor.CMD_TEXT_KEYFRAME: list(range(47)), # All text keyframe params
LottieTensor.CMD_FONT: list(range(23)), # All font params
LottieTensor.CMD_CHAR: list(range(35)), # All char params
LottieTensor.CMD_WIDTH_KEYFRAME: [
LottieTensor.Index.WidthKeyframe.T,
LottieTensor.Index.WidthKeyframe.S,
LottieTensor.Index.WidthKeyframe.I_X,
LottieTensor.Index.WidthKeyframe.I_Y,
LottieTensor.Index.WidthKeyframe.O_X,
LottieTensor.Index.WidthKeyframe.O_Y
],
LottieTensor.CMD_COLOR_KEYFRAME: [
LottieTensor.Index.Keyframe.T,
LottieTensor.Index.Keyframe.S1,
LottieTensor.Index.Keyframe.S2,
LottieTensor.Index.Keyframe.S3,
LottieTensor.Index.Keyframe.E1,
LottieTensor.Index.Keyframe.I_X,
LottieTensor.Index.Keyframe.I_Y,
LottieTensor.Index.Keyframe.O_X,
LottieTensor.Index.Keyframe.O_Y
],
LottieTensor.CMD_OPACITY_KEYFRAME: [
LottieTensor.Index.Keyframe.T,
LottieTensor.Index.Keyframe.S1,
LottieTensor.Index.Keyframe.I_X,
LottieTensor.Index.Keyframe.I_Y,
LottieTensor.Index.Keyframe.O_X,
LottieTensor.Index.Keyframe.O_Y
],
LottieTensor.CMD_OPACITY_ANIMATED: [],
LottieTensor.CMD_TM: [
LottieTensor.Index.Tm.A
],
LottieTensor.CMD_VALUE: [
LottieTensor.Index.Value.VALUE
],
LottieTensor.CMD_SKEW: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_SKEW_AXIS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_STAR: [
LottieTensor.Index.Star.D,
LottieTensor.Index.Star.SY
],
LottieTensor.CMD_INNER_RADIUS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_OUTER_RADIUS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_INNER_ROUNDNESS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_OUTER_ROUNDNESS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_POINTS_STAR: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_STAR_ROTATION: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_MULTIPLE: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_REPEATER: [
LottieTensor.Index.Repeater.IX
],
LottieTensor.CMD_COPIES: [
LottieTensor.Index.SingleValue.VALUE,
LottieTensor.Index.SingleValue.IX
],
LottieTensor.CMD_REPEATER_OFFSET: [
LottieTensor.Index.SingleValue.VALUE,
LottieTensor.Index.SingleValue.IX
],
LottieTensor.CMD_COMPOSITE: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_REPEATER_TRANSFORM: [],
LottieTensor.CMD_TR_P_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_A_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_SCALE: [
LottieTensor.Index.TwoValues.VALUE1,
LottieTensor.Index.TwoValues.VALUE2
],
LottieTensor.CMD_TR_S_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_R_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_SO_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_TR_EO_IX: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_MORE_OPTIONS: [
LottieTensor.Index.MoreOptions.G,
LottieTensor.Index.MoreOptions.ALIGNMENT_A,
LottieTensor.Index.MoreOptions.ALIGNMENT_K1,
LottieTensor.Index.MoreOptions.ALIGNMENT_K2,
LottieTensor.Index.MoreOptions.ALIGNMENT_IX
],
LottieTensor.CMD_GRADIENT_FILL: [],
LottieTensor.CMD_START_POINT: [
LottieTensor.Index.TwoValues.VALUE1,
LottieTensor.Index.TwoValues.VALUE2
],
LottieTensor.CMD_END_POINT: [
LottieTensor.Index.TwoValues.VALUE1,
LottieTensor.Index.TwoValues.VALUE2
],
LottieTensor.CMD_GRADIENT_TYPE: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_HIGHLIGHT_LENGTH: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_HIGHLIGHT_ANGLE: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_ORIGINAL_COLORS: list(range(48)), # All color values + count
LottieTensor.CMD_COLOR_POINTS: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_GRADIENT_STROKE: [],
LottieTensor.CMD_WIDTH: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_LINE_CAP: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_LINE_JOIN: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_MITER_LIMIT: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_ML2: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_COLOR: [
LottieTensor.Index.Color.INDEX,
LottieTensor.Index.Color.R,
LottieTensor.Index.Color.G,
LottieTensor.Index.Color.B
],
LottieTensor.CMD_EFFECT: [
LottieTensor.Index.Effect.TYPE,
LottieTensor.Index.Effect.INDEX,
LottieTensor.Index.Effect.NP,
LottieTensor.Index.Effect.ENABLED
],
LottieTensor.CMD_LAYER_EFFECT: [
LottieTensor.Index.LayerEffect.INDEX,
LottieTensor.Index.LayerEffect.VALUE
],
LottieTensor.CMD_DROPDOWN: [
LottieTensor.Index.Dropdown.INDEX,
LottieTensor.Index.Dropdown.VALUE
],
LottieTensor.CMD_NO_VALUE: [
LottieTensor.Index.NO_VALUE.INDEX,
LottieTensor.Index.NO_VALUE.VALUE
],
LottieTensor.CMD_IGNORED: [
LottieTensor.Index.Ignored.INDEX,
LottieTensor.Index.Ignored.VALUE
],
LottieTensor.CMD_SLIDER: [
LottieTensor.Index.Slider.INDEX,
LottieTensor.Index.Slider.VALUE
],
LottieTensor.CMD_FILL_RULE: [
LottieTensor.Index.SingleValue.VALUE
],
LottieTensor.CMD_MERGE: [],
LottieTensor.CMD_MERGE_MODE: [
LottieTensor.Index.MergeMode.MODE
],
LottieTensor.CMD_MASKS_PROPERTIES: [],
LottieTensor.CMD_MASK: [
LottieTensor.Index.Mask.INDEX,
LottieTensor.Index.Mask.INV,
LottieTensor.Index.Mask.MODE
],
LottieTensor.CMD_MASK_PT: [
LottieTensor.Index.MaskPt.A,
LottieTensor.Index.MaskPt.IX
],
LottieTensor.CMD_MASK_PT_K: [],
LottieTensor.CMD_MASK_PT_K_C: [
LottieTensor.Index.MaskPtK.C
],
LottieTensor.CMD_MASK_PT_K_I: list(range(21)), # V1-V20 + COUNT
LottieTensor.CMD_MASK_PT_K_O: list(range(21)),
LottieTensor.CMD_MASK_PT_K_V: list(range(21)),
LottieTensor.CMD_MASK_O: [
LottieTensor.Index.MaskO.A,
LottieTensor.Index.MaskO.K,
LottieTensor.Index.MaskO.IX
],
LottieTensor.CMD_MASK_X: [
LottieTensor.Index.MaskX.A,
LottieTensor.Index.MaskX.K,
LottieTensor.Index.MaskX.IX
],
LottieTensor.CMD_MASK_PT_K_ARRAY: [],
LottieTensor.CMD_MASK_PT_KEYFRAME: [
LottieTensor.Index.MaskPtKeyframe.INDEX,
LottieTensor.Index.MaskPtKeyframe.T
],
LottieTensor.CMD_MASK_PT_KF_I: [
LottieTensor.Index.MaskPtKfI.X,
LottieTensor.Index.MaskPtKfI.Y
],
LottieTensor.CMD_MASK_PT_KF_O: [
LottieTensor.Index.MaskPtKfO.X,
LottieTensor.Index.MaskPtKfO.Y
],
LottieTensor.CMD_MASK_PT_KF_S: [],
LottieTensor.CMD_MASK_PT_KF_SHAPE: [
LottieTensor.Index.MaskPtKfShape.INDEX,
LottieTensor.Index.MaskPtKfShape.C
],
LottieTensor.CMD_MASK_PT_KF_SHAPE_I: list(range(21)),
LottieTensor.CMD_MASK_PT_KF_SHAPE_O: list(range(21)),
LottieTensor.CMD_MASK_PT_KF_SHAPE_V: list(range(21)),
LottieTensor.CMD_TR_POSITION: [
LottieTensor.Index.TrPosition.X,
LottieTensor.Index.TrPosition.Y
],
LottieTensor.CMD_TR_ANCHOR: [
LottieTensor.Index.TrAnchor.X,
LottieTensor.Index.TrAnchor.Y
],
LottieTensor.CMD_TR_ROTATION: [
LottieTensor.Index.TrRotation.VALUE
],
LottieTensor.CMD_TR_START_OPACITY: [
LottieTensor.Index.TrStartOpacity.VALUE
],
LottieTensor.CMD_TR_END_OPACITY: [
LottieTensor.Index.TrEndOpacity.VALUE
],
LottieTensor.CMD_ZIG_ZAG: [
LottieTensor.Index.ZigZag.IX
],
LottieTensor.CMD_FREQUENCY: [
LottieTensor.Index.Frequency.VALUE
],
LottieTensor.CMD_AMPLITUDE: [
LottieTensor.Index.Amplitude.VALUE
],
LottieTensor.CMD_POINT_TYPE: [
LottieTensor.Index.PointType.VALUE
],
LottieTensor.CMD_ANIMATORS: [],
LottieTensor.CMD_ANIMATOR: [],
LottieTensor.CMD_RANGE_SELECTOR: [
LottieTensor.Index.RangeSelector.T,
LottieTensor.Index.RangeSelector.R,
LottieTensor.Index.RangeSelector.B,
LottieTensor.Index.RangeSelector.SH,
LottieTensor.Index.RangeSelector.RN
],
LottieTensor.CMD_RANGE_START: [
LottieTensor.Index.RangeStart.A
],
LottieTensor.CMD_RANGE_START_KEYFRAME: [
LottieTensor.Index.RangeStartKeyframe.T,
LottieTensor.Index.RangeStartKeyframe.S,
LottieTensor.Index.RangeStartKeyframe.I_X,
LottieTensor.Index.RangeStartKeyframe.I_Y,
LottieTensor.Index.RangeStartKeyframe.O_X,
LottieTensor.Index.RangeStartKeyframe.O_Y
],
LottieTensor.CMD_AMOUNT: [
LottieTensor.Index.Amount.A,
LottieTensor.Index.Amount.K,
LottieTensor.Index.Amount.IX
],
LottieTensor.CMD_MAX_EASE: [
LottieTensor.Index.MaxEase.A,
LottieTensor.Index.MaxEase.K,
LottieTensor.Index.MaxEase.IX
],
LottieTensor.CMD_MIN_EASE: [
LottieTensor.Index.MinEase.A,
LottieTensor.Index.MinEase.K,
LottieTensor.Index.MinEase.IX
],
LottieTensor.CMD_ANIMATOR_PROPERTIES: [],
LottieTensor.CMD_RADIUS: [
LottieTensor.Index.Radius.VALUE
],
LottieTensor.CMD_RANGE_END: [
LottieTensor.Index.RangeEnd.A
],
LottieTensor.CMD_RANGE_END_KEYFRAME: [
LottieTensor.Index.RangeEndKeyframe.T,
LottieTensor.Index.RangeEndKeyframe.S,
LottieTensor.Index.RangeEndKeyframe.I_X,
LottieTensor.Index.RangeEndKeyframe.I_Y,
LottieTensor.Index.RangeEndKeyframe.O_X,
LottieTensor.Index.RangeEndKeyframe.O_Y
],
LottieTensor.CMD_RANGE_OFFSET: [
LottieTensor.Index.Amount.A,
LottieTensor.Index.Amount.K,
LottieTensor.Index.Amount.IX
],
LottieTensor.CMD_RANGE_OFFSET_KEYFRAME: [
LottieTensor.Index.RangeOffsetKeyframe.T,
LottieTensor.Index.RangeOffsetKeyframe.S,
LottieTensor.Index.RangeOffsetKeyframe.I_X,
LottieTensor.Index.RangeOffsetKeyframe.I_Y,
LottieTensor.Index.RangeOffsetKeyframe.O_X,
LottieTensor.Index.RangeOffsetKeyframe.O_Y
],
LottieTensor.CMD_S_M: [
LottieTensor.Index.SM.A,
LottieTensor.Index.SM.K,
LottieTensor.Index.SM.IX
],
LottieTensor.CMD_OPACITY_ANIMATORS: [
LottieTensor.Index.OpacityAnimators.A,
LottieTensor.Index.OpacityAnimators.K,
LottieTensor.Index.OpacityAnimators.IX
],
LottieTensor.CMD_SCALE_ANIMATORS: [
LottieTensor.Index.ScaleAnimators.A,
LottieTensor.Index.ScaleAnimators.K_X,
LottieTensor.Index.ScaleAnimators.K_Y,
LottieTensor.Index.ScaleAnimators.K_Z,
LottieTensor.Index.ScaleAnimators.IX
],
LottieTensor.CMD_ROTATION_ANIMATORS: [
LottieTensor.Index.RotationAnimators.A,
LottieTensor.Index.RotationAnimators.K,
LottieTensor.Index.RotationAnimators.IX
],
LottieTensor.CMD_POSITION_ANIMATORS: [
LottieTensor.Index.PositionAnimators.A,
LottieTensor.Index.PositionAnimators.K_X,
LottieTensor.Index.PositionAnimators.K_Y,
LottieTensor.Index.PositionAnimators.K_Z,
LottieTensor.Index.PositionAnimators.IX
],
LottieTensor.CMD_TRACKING_ANIMATORS: [
LottieTensor.Index.TrackingAnimators.A,
LottieTensor.Index.TrackingAnimators.K,
LottieTensor.Index.TrackingAnimators.IX
],
LottieTensor.CMD_DASHES: [],
LottieTensor.CMD_DASH: [
LottieTensor.Index.Dash.TYPE,
LottieTensor.Index.Dash.LENGTH,
LottieTensor.Index.Dash.V_IX
],
LottieTensor.CMD_DASH_ANIMATED: [
LottieTensor.Index.DashAnimated.TYPE,
LottieTensor.Index.DashAnimated.V_IX
],
LottieTensor.CMD_DASH_KEYFRAME: [
LottieTensor.Index.DashKeyframe.T,
LottieTensor.Index.DashKeyframe.S,
LottieTensor.Index.DashKeyframe.I_X,
LottieTensor.Index.DashKeyframe.I_Y,
LottieTensor.Index.DashKeyframe.O_X,
LottieTensor.Index.DashKeyframe.O_Y
],
LottieTensor.CMD_DASH_OFFSET: [
LottieTensor.Index.DashOffset.O
],
LottieTensor.CMD_WIDTH_ANIMATED: [],
# All end commands have empty param lists
LottieTensor.CMD_POSITION_END: [],
LottieTensor.CMD_SCALE_END: [],
LottieTensor.CMD_ROTATION_END: [],
LottieTensor.CMD_OPACITY_END: [],
LottieTensor.CMD_ANCHOR_END: [],
LottieTensor.CMD_GROUP_END: [],
LottieTensor.CMD_TRANSFORM_END: [],
LottieTensor.CMD_LAYER_END: [],
LottieTensor.CMD_PATH_END: [],
LottieTensor.CMD_RECT_END: [],
LottieTensor.CMD_ELLIPSE_END: [],
LottieTensor.CMD_STAR_END: [],
LottieTensor.CMD_TRIM_END: [],
LottieTensor.CMD_REPEATER_END: [],
LottieTensor.CMD_REPEATER_TRANSFORM_END: [],
LottieTensor.CMD_GRADIENT_FILL_END: [],
LottieTensor.CMD_GRADIENT_STROKE_END: [],
LottieTensor.CMD_MERGE_END: [],
LottieTensor.CMD_ROUNDED_CORNERS_END: [],
LottieTensor.CMD_TWIST_END: [],
LottieTensor.CMD_BEZIER_END: [],
LottieTensor.CMD_TEXT_LAYER_END: [],
LottieTensor.CMD_TEXT_DATA_END: [],
LottieTensor.CMD_SOLID_LAYER_END: [],
LottieTensor.CMD_NULL_LAYER_END: [],
LottieTensor.CMD_PRECOMP_LAYER_END: [],
LottieTensor.CMD_POSITION_X_END: [],
LottieTensor.CMD_POSITION_Y_END: [],
LottieTensor.CMD_POSITION_Z_END: [],
LottieTensor.CMD_SCALE_X_END: [],
LottieTensor.CMD_SCALE_Y_END: [],
LottieTensor.CMD_SCALE_Z_END: [],
LottieTensor.CMD_ROTATION_X_END: [],
LottieTensor.CMD_ROTATION_Y_END: [],
LottieTensor.CMD_ROTATION_Z_END: [],
LottieTensor.CMD_EFFECTS_END: [],
LottieTensor.CMD_EFFECT_END: [],
LottieTensor.CMD_KEYFRAME_END: [],
LottieTensor.CMD_WIDTH_ANIMATED_END: [],
LottieTensor.CMD_FONTS_END: [],
LottieTensor.CMD_CHARS_END: [],
LottieTensor.CMD_CHAR_END: [],
LottieTensor.CMD_CHAR_SHAPES_END: [],
LottieTensor.CMD_TEXT_KEYFRAMES_END: [],
LottieTensor.CMD_TEXT_DOC_END: [],
LottieTensor.CMD_MORE_OPTIONS_END: [],
LottieTensor.CMD_OPACITY_ANIMATED_END: [],
LottieTensor.CMD_MASKS_PROPERTIES_END: [],
LottieTensor.CMD_MASK_END: [],
LottieTensor.CMD_MASK_PT_END: [],
LottieTensor.CMD_MASK_PT_K_END: [],
LottieTensor.CMD_TM_END: [],
LottieTensor.CMD_MASK_PT_K_ARRAY_END: [],
LottieTensor.CMD_MASK_PT_KEYFRAME_END: [],
LottieTensor.CMD_MASK_PT_KF_S_END: [],
LottieTensor.CMD_MASK_PT_KF_SHAPE_END: [],
LottieTensor.CMD_VALUE_END: [],
LottieTensor.CMD_ZIG_ZAG_END: [],
LottieTensor.CMD_ANIMATORS_END: [],
LottieTensor.CMD_ANIMATOR_END: [],
LottieTensor.CMD_RANGE_SELECTOR_END: [],
LottieTensor.CMD_RANGE_START_END: [],
LottieTensor.CMD_RANGE_END_END: [],
LottieTensor.CMD_END_END: [],
LottieTensor.CMD_START_END: [],
LottieTensor.CMD_OFFSET_END: [],
LottieTensor.CMD_RANGE_OFFSET_END: [],
LottieTensor.CMD_SCALE_ANIMATORS_END: [],
LottieTensor.CMD_ROTATION_ANIMATORS_END: [],
LottieTensor.CMD_POSITION_ANIMATORS_END: [],
LottieTensor.CMD_OPACITY_ANIMATORS_END: [],
LottieTensor.CMD_COLOR_ANIMATED_END: [],
LottieTensor.CMD_DASHES_END: [],
LottieTensor.CMD_DASH_ANIMATED_END: [],
LottieTensor.CMD_SIZE_END: [],
LottieTensor.CMD_RECT_ROUNDED_END: [],
LottieTensor.CMD_ANIMATOR_PROPERTIES_END: [],
LottieTensor.CMD_ASSET_END: [],
LottieTensor.CMD_EFFECTS: [],
LottieTensor.CMD_FONTS: [],
LottieTensor.CMD_CHARS: [],
LottieTensor.CMD_CHAR_SHAPES: [],
LottieTensor.CMD_TEXT_KEYFRAMES: [],
LottieTensor.CMD_TEXT_DATA: [],
LottieTensor.CMD_DOCUMENT: [],
LottieTensor.CMD_TEXT_DOC: [],
}
# Commands without parameters
empty_param_cmds = {k for k, v in param_orders.items() if not v}
return param_orders.get(cmd_idx, [])
@staticmethod
def get_vocab_range_for_offset(offset: int) -> tuple:
"""Get the vocabulary range (start, end) for a given offset."""
vocab_ranges = {
0: (1, 151643), # NO_OFFSET (tokenizer tokens)
155000: (153000, 157000), # TIME_OFFSET: -2000 to 2000
159100: (157100, 161100), # SPACE_OFFSET: -2000 to 2000
161200: (161200, 161220), # AMPLITUDE_OFFSET: 0 to 20
161300: (161300, 165300), # ANCHOR_OFFSET: -2000 to 2000
165400: (165400, 165401), # ANIMATED_OFFSET: 0 to 1
165402: (165402, 165403), # H_FLAG_OFFSET: 0 to 1
165404: (165404, 165405), # OFFSET_VAL_OFFSET: 0 to 1
165406: (165406, 165408), # CA_OFFSET: 0 to 2
165409: (165409, 165415), # JUSTIFY_OFFSET: 0 to 6
165416: (165416, 166016), # TEXT_TRACKING_OFFSET: -100 to 500
166017: (166017, 166018), # HAS_STROKE_COLOR_OFFSET: 0 to 1
166019: (166019, 167019), # IX_OFFSET: 0 to 1000
167020: (167020, 167040), # BM_OFFSET: 0 to 20
167041: (167041, 167042), # CLOSED_OFFSET: 0 to 1
167043: (167043, 167048), # DIRECTION_OFFSET: 0 to 5
167049: (167049, 167054), # STAR_TYPE_OFFSET: 0 to 5
167055: (167055, 167060), # MULTIPLE_OFFSET: 0 to 5
167061: (167061, 167066), # COMPOSITE_OFFSET: 0 to 5
167067: (167067, 167117), # SKEW_OFFSET: -25 to 25
167118: (167118, 167168), # SKEW_AXIS_OFFSET: -25 to 25
167169: (167169, 170169), # SCALE_OFFSET: -1000 to 2000
170170: (170170, 171610), # ROTATION_OFFSET: -720 to 720
171611: (171611, 171811), # EASE_OFFSET: -100 to 100
171812: (171812, 171912), # SMOOTH_OFFSET: 0 to 100
171913: (171913, 172013), # TRACKING_OFFSET: -50 to 50
172014: (172014, 173014), # INDEX_OFFSET: 0 to 1000
173015: (173015, 173016), # DDD_OFFSET: 0 to 1
173017: (173017, 173018), # HD_OFFSET: 0 to 1
173019: (173019, 173069), # CP_OFFSET: 0 to 50
173070: (173070, 173071), # HAS_MASK_OFFSET: 0 to 1
173072: (173072, 173073), # AO_OFFSET: 0 to 1
173074: (173074, 173079), # TT_OFFSET: 0 to 5
173080: (173080, 173180), # TP_OFFSET: 0 to 100
173181: (173181, 173183), # TD_OFFSET: 0 to 2
173184: (173184, 173185), # CT_OFFSET: 0 to 1
173186: (173186, 173686), # NUMBER_OFFSET: 0 to 500
173687: (173687, 173697), # DIM_OFFSET: 0 to 10
173698: (173698, 173699), # HAS_C_A_OFFSET: 0 to 1
173700: (173700, 173701), # HAS_C_IX_OFFSET: 0 to 1
173702: (173702, 173703), # HAS_O_A_OFFSET: 0 to 1
173704: (173704, 173705), # HAS_O_IX_OFFSET: 0 to 1
173706: (173706, 173710), # FILL_RULE_OFFSET: 0 to 4
173711: (173711, 173751), # TYPE_OFFSET: 0 to 40
173752: (173752, 173762), # TEXT_RANGE_UNITS_OFFSET: 0 to 10
173763: (173763, 173764), # INV_OFFSET: 0 to 1
173765: (173765, 173775), # MODE_OFFSET: 0 to 10
173776: (173776, 173786), # TEXT_SHAPE_TYPE_OFFSET: 0 to 10
173787: (173787, 173788), # TEXT_RANDOM_OFFSET: 0 to 1
173789: (173789, 173839), # COLOR_POINTS_OFFSET: 0 to 50
173840: (173840, 174940), # ROUND_OFFSET: -100 to 1000
174941: (174941, 175241), # RADIUS_OFFSET: 0 to 300
175242: (175242, 175392), # FREQUENCY_OFFSET: 0 to 150
175393: (175393, 177393), # SPEED_OFFSET: -1000 to 1000
177394: (177394, 179494), # FONT_OFFSET: -100 to 2000
179495: (179495, 179750), # COLOR_OFFSET: 0 to 255
179752: (179752, 179754), # LINE_CAP_OFFSET: 1 to 3
179757: (179757, 179759), # LINE_JOIN_OFFSET: 1 to 3
179760: (179760, 179860), # MITER_LIMIT_OFFSET: 0 to 100
179861: (179861, 181111), # EFFECT_OFFSET: -250 to 1000
181112: (181112, 181212), # OPACITY_OFFSET: 0 to 100
181300: (181300, 191300), # WIDTH_VALUE_OFFSET: 0 to 10000 (新增)
}
return vocab_ranges.get(offset, (0, 0))
@staticmethod
def _find_nearest_layer_end(flattened: List[int], max_length: int, command_offset: int) -> int:
"""
在flattened list中查找最接近max_length的layer end位置
智能截断规则:
1. 从max_length位置向前搜索,找最近的layer end
2. 必须确保至少有ANIMATION命令和一个完整的layer
3. layer end包括: LAYER_END, PRECOMP_LAYER_END, TEXT_LAYER_END, NULL_LAYER_END, SOLID_LAYER_END
4. 如果找不到合适的位置,返回-1(表示放弃样本)
Args:
flattened: 扁平化的token列表
max_length: 目标最大长度
command_offset: 命令token的offset (151936)
Returns:
最近的layer end位置(截断到这里),如果找不到返回-1
"""
# Layer end命令集合
LAYER_END_CMDS = {
LottieTensor.CMD_LAYER_END, # 27
LottieTensor.CMD_PRECOMP_LAYER_END, # 46
LottieTensor.CMD_TEXT_LAYER_END, # 90
LottieTensor.CMD_NULL_LAYER_END, # 44
LottieTensor.CMD_SOLID_LAYER_END, # 95
}
# 向前搜索范围:从max_length向前最多搜索2000个token
# 2000个token大约能包含1-2个完整的layer
search_start = max(0, max_length - 2000)
best_pos = -1
# 从max_length位置向前搜索
for i in range(min(max_length - 1, len(flattened) - 1), search_start - 1, -1):
token = flattened[i]
# 检查是否是命令token
if token >= command_offset and token < command_offset + len(LottieTensor.COMMANDS):
cmd_idx = token - command_offset
if cmd_idx in LAYER_END_CMDS:
# 找到layer end,截断点是这个token之后
candidate_pos = i + 1
# 验证截断后的序列是否完整(必须有ANIMATION和至少一个layer
if LottieTensor._validate_truncated_sequence(flattened[:candidate_pos], command_offset):
best_pos = candidate_pos
break
return best_pos
@staticmethod
def _validate_truncated_sequence(flattened: List[int], command_offset: int) -> bool:
"""
验证截断后的序列是否完整有效
要求:
1. 必须有ANIMATION命令
2. 必须至少有一个完整的layer(有layer start和layer end配对)
3. layers不能为空
4. 【新增】必须有主layers(不能只有assets中的layers
Args:
flattened: 截断后的token列表
command_offset: 命令token的offset
Returns:
是否是有效的序列
"""
has_animation = False
layer_count = 0
asset_depth = 0 # 跟踪是否在asset内部
main_layer_count = 0 # 主layers计数(不在asset内的layer
# Layer start和end命令
LAYER_START_CMDS = {
LottieTensor.CMD_LAYER, # 26 - ShapeLayer
LottieTensor.CMD_PRECOMP_LAYER, # 45
LottieTensor.CMD_TEXT_LAYER, # 89
LottieTensor.CMD_NULL_LAYER, # 43
LottieTensor.CMD_SOLID_LAYER, # 94
}
LAYER_END_CMDS = {
LottieTensor.CMD_LAYER_END,
LottieTensor.CMD_PRECOMP_LAYER_END,
LottieTensor.CMD_TEXT_LAYER_END,
LottieTensor.CMD_NULL_LAYER_END,
LottieTensor.CMD_SOLID_LAYER_END,
}
layer_stack = 0 # 跟踪layer的嵌套深度
for token in flattened:
if token >= command_offset and token < command_offset + len(LottieTensor.COMMANDS):
cmd_idx = token - command_offset
if cmd_idx == LottieTensor.CMD_ANIMATION:
has_animation = True
elif cmd_idx == LottieTensor.CMD_ASSET: # 进入asset
asset_depth += 1
elif cmd_idx == LottieTensor.CMD_ASSET_END: # 离开asset
if asset_depth > 0:
asset_depth -= 1
elif cmd_idx in LAYER_START_CMDS:
layer_stack += 1
elif cmd_idx in LAYER_END_CMDS:
if layer_stack > 0:
layer_stack -= 1
layer_count += 1 # 完成一个完整的layer
# 如果不在asset内部,这是主layer
if asset_depth == 0:
main_layer_count += 1
# 验证条件:
# 1. 有ANIMATION命令
# 2. 至少有一个完整的layerlayer_count >= 1
# 3. 所有layer都已正确闭合(layer_stack == 0
# 4. 【新增】至少有一个主layermain_layer_count >= 1),防止只有assets没有主layers
return has_animation and layer_count >= 1 and layer_stack == 0 and main_layer_count >= 1
def flatten_to_list(lottie_tensor: 'LottieTensor', max_length: int = None) -> List[int]:
"""
Flatten LottieTensor to a 1D list with proper offsets.
紧凑格式:不使用 SKIP_TOKEN,只输出有意义的参数。
"""
COMMAND_OFFSET = 151936
NUMBER_OFFSET = 173186
NUM_COMMANDS = len(LottieTensor.COMMANDS)
# Essential parameter counts - params before this index must always be output
ESSENTIAL_PARAM_COUNT = {
LottieTensor.CMD_ANIMATION: 6,
LottieTensor.CMD_LAYER: 4,
LottieTensor.CMD_NULL_LAYER: 4,
LottieTensor.CMD_PRECOMP_LAYER: 4,
LottieTensor.CMD_TEXT_LAYER: 4,
LottieTensor.CMD_SOLID_LAYER: 6,
LottieTensor.CMD_KEYFRAME: 1,
LottieTensor.CMD_WIDTH_KEYFRAME: 2,
LottieTensor.CMD_COLOR_KEYFRAME: 5,
LottieTensor.CMD_OPACITY_KEYFRAME: 2,
LottieTensor.CMD_POINT: 2,
LottieTensor.CMD_FILL: 3,
LottieTensor.CMD_STROKE: 3,
LottieTensor.CMD_TRANSFORM_SHAPE: 8,
LottieTensor.CMD_GROUP: 1,
LottieTensor.CMD_PATH: 4,
LottieTensor.CMD_POSITION: 1,
LottieTensor.CMD_SCALE: 1,
LottieTensor.CMD_ROTATION: 1,
LottieTensor.CMD_OPACITY: 1,
LottieTensor.CMD_ANCHOR: 1,
LottieTensor.CMD_SIZE: 1,
LottieTensor.CMD_RECT: 1,
LottieTensor.CMD_STAR: 2,
LottieTensor.CMD_TRIM: 1,
LottieTensor.CMD_REPEATER: 1,
LottieTensor.CMD_MASK: 3,
LottieTensor.CMD_MASK_PT: 2,
LottieTensor.CMD_MASK_O: 3,
LottieTensor.CMD_MASK_X: 3,
LottieTensor.CMD_RANGE_SELECTOR: 5,
LottieTensor.CMD_RANGE_START: 1,
LottieTensor.CMD_RANGE_END: 1,
LottieTensor.CMD_RANGE_OFFSET: 1,
LottieTensor.CMD_AMOUNT: 3,
LottieTensor.CMD_EFFECT: 2,
LottieTensor.CMD_COLOR: 4,
LottieTensor.CMD_GRADIENT_TYPE: 1,
LottieTensor.CMD_TR_SCALE: 2,
LottieTensor.CMD_TR_POSITION: 2,
LottieTensor.CMD_TR_ANCHOR: 2,
LottieTensor.CMD_ORIGINAL_COLORS: 0,
LottieTensor.CMD_DASH_KEYFRAME: 2,
LottieTensor.CMD_RANGE_START_KEYFRAME: 2,
LottieTensor.CMD_RANGE_END_KEYFRAME: 2,
LottieTensor.CMD_RANGE_OFFSET_KEYFRAME: 2,
LottieTensor.CMD_ELLIPSE_SIZE: 1,
LottieTensor.CMD_RECT_SIZE: 1,
}
# Parameters with default value 0 (not PAD_VAL)
ZERO_DEFAULT_PARAMS = {
LottieTensor.CMD_KEYFRAME: {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25},
LottieTensor.CMD_WIDTH_KEYFRAME: {2, 3, 4, 5},
LottieTensor.CMD_COLOR_KEYFRAME: {5, 6, 7, 8},
LottieTensor.CMD_OPACITY_KEYFRAME: {2, 3, 4, 5},
LottieTensor.CMD_DASH_KEYFRAME: {2, 3, 4, 5},
LottieTensor.CMD_RANGE_START_KEYFRAME: {2, 3, 4, 5},
LottieTensor.CMD_RANGE_END_KEYFRAME: {2, 3, 4, 5},
LottieTensor.CMD_RANGE_OFFSET_KEYFRAME: {2, 3, 4, 5},
LottieTensor.CMD_POINT: {2, 3, 4, 5},
LottieTensor.CMD_ANIMATION: {5},
LottieTensor.CMD_LAYER: {4, 5, 6, 7, 8, 9, 10, 11, 12},
LottieTensor.CMD_NULL_LAYER: {4, 5, 6, 7, 8, 9, 10, 11},
LottieTensor.CMD_PRECOMP_LAYER: {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14},
LottieTensor.CMD_FILL: {3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14}, # Removed 8 (FILL_RULE) - should not default to 0
LottieTensor.CMD_STROKE: {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13},
LottieTensor.CMD_GROUP: {1, 2, 3, 4},
LottieTensor.CMD_PATH: {4, 5},
LottieTensor.CMD_TRANSFORM_SHAPE: {8, 9, 10},
LottieTensor.CMD_RECT: {1},
LottieTensor.CMD_POSITION: {1, 2, 3},
LottieTensor.CMD_SCALE: {1, 2, 3},
LottieTensor.CMD_ROTATION: {1},
LottieTensor.CMD_OPACITY: {1},
LottieTensor.CMD_ANCHOR: {1, 2, 3},
LottieTensor.CMD_MASK_PT_K_I: set(range(21)),
LottieTensor.CMD_MASK_PT_K_O: set(range(21)),
LottieTensor.CMD_MASK_PT_K_V: set(range(21)),
LottieTensor.CMD_MASK_PT_KF_SHAPE_I: set(range(21)),
LottieTensor.CMD_MASK_PT_KF_SHAPE_O: set(range(21)),
LottieTensor.CMD_MASK_PT_KF_SHAPE_V: set(range(21)),
}
# SIZE类命令集合
SIZE_COMMANDS = {
LottieTensor.CMD_SIZE,
LottieTensor.CMD_ELLIPSE_SIZE,
LottieTensor.CMD_RECT_SIZE
}
TOKENIZER_COMMANDS = {
LottieTensor.CMD_FONT: {
'regular': [LottieTensor.Index.Font.ASCENT],
'token_groups': [
(LottieTensor.Index.Font.FAMILY_TOKEN_COUNT,
LottieTensor.Index.Font.FAMILY_TOKEN_0, 10),
(LottieTensor.Index.Font.STYLE_TOKEN_COUNT,
LottieTensor.Index.Font.STYLE_TOKEN_0, 10)
]
},
LottieTensor.CMD_CHAR: {
'regular': [
LottieTensor.Index.Char.SIZE,
LottieTensor.Index.Char.W
],
'token_groups': [
(LottieTensor.Index.Char.CH_TOKEN_COUNT,
LottieTensor.Index.Char.CH_TOKEN_0, 10),
(LottieTensor.Index.Char.STYLE_TOKEN_COUNT,
LottieTensor.Index.Char.STYLE_TOKEN_0, 10),
(LottieTensor.Index.Char.FAMILY_TOKEN_COUNT,
LottieTensor.Index.Char.FAMILY_TOKEN_0, 10)
]
},
LottieTensor.CMD_ASSET: {
'regular': [LottieTensor.Index.Asset.FR],
'token_groups': [
(LottieTensor.Index.Asset.ID_TOKEN_COUNT,
LottieTensor.Index.Asset.ID_TOKEN_0, 10)
]
},
LottieTensor.CMD_REFERENCE_ID: {
'regular': [],
'token_groups': [
(LottieTensor.Index.ReferenceId.ID_TOKEN_COUNT,
LottieTensor.Index.ReferenceId.ID_TOKEN_0, 10)
]
},
LottieTensor.CMD_TEXT_KEYFRAME: {
'regular': list(range(LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKENS_START)),
'token_groups': [
(LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKEN_COUNT,
LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKENS_START, 10),
(LottieTensor.Index.TextKeyframe.TEXT_TOKEN_COUNT,
LottieTensor.Index.TextKeyframe.TEXT_TOKENS_START, 15)
]
}
}
# 【修复】改进的 ANIMATED 值判断函数 - 使用更严格的阈值
def is_animated_value(value):
"""判断一个值是否表示动画状态"""
if value == LottieTensor.PAD_VAL:
return False
# 使用 0.5 作为阈值,任何 >= 0.5 的值都视为 animated
return value >= 0.5
def get_param_default(cmd_idx, param_pos, params):
"""Get default value for a parameter."""
# 对于SIZE类命令的特殊处理
if cmd_idx in SIZE_COMMANDS:
# 检查ANIMATED状态
animated_val = params[LottieTensor.Index.Transform.ANIMATED] if len(params) > 0 else LottieTensor.PAD_VAL
is_animated = animated_val != LottieTensor.PAD_VAL and is_animated_value(animated_val)
if param_pos == 0: # ANIMATED参数本身
return LottieTensor.PAD_VAL
elif param_pos in {1, 2}: # X 和 Y 参数
if is_animated:
# ANIMATED=1时,X和Y不应该输出(由keyframe提供)
return LottieTensor.PAD_VAL
else:
# ANIMATED=0时,X和Y默认为0
return 0.0
zero_set = ZERO_DEFAULT_PARAMS.get(cmd_idx, set())
if param_pos in zero_set:
return 0.0
return LottieTensor.PAD_VAL
def is_meaningful_value(value, default):
"""Check if a value is meaningful (not default)."""
if value == LottieTensor.PAD_VAL:
return False
if default == LottieTensor.PAD_VAL:
return True
return abs(value - default) > 1e-6
flattened = []
SIZE_END_COMMANDS = {
LottieTensor.CMD_SIZE: LottieTensor.CMD_SIZE_END,
LottieTensor.CMD_ELLIPSE_SIZE: LottieTensor.CMD_SIZE_END,
LottieTensor.CMD_RECT_SIZE: LottieTensor.CMD_SIZE_END,
}
# 【新增】前瞻检测:记录每个SIZE命令后面是否有keyframe
size_cmd_has_keyframe = {}
for i in range(lottie_tensor.seq_len.item()):
cmd_idx = int(lottie_tensor.commands[i].item())
if cmd_idx in SIZE_COMMANDS:
# 检查后续是否有keyframe(在遇到结束标记或其他形状命令之前)
has_kf = False
end_cmd = SIZE_END_COMMANDS.get(cmd_idx, LottieTensor.CMD_SIZE_END)
# 定义会结束 size 上下文的命令
context_end_cmds = {
end_cmd,
LottieTensor.CMD_FILL,
LottieTensor.CMD_STROKE,
LottieTensor.CMD_GROUP_END,
LottieTensor.CMD_ELLIPSE_END,
LottieTensor.CMD_RECT_END,
}
for j in range(i + 1, lottie_tensor.seq_len.item()):
next_cmd = int(lottie_tensor.commands[j].item())
if next_cmd in context_end_cmds:
break
if next_cmd == LottieTensor.CMD_KEYFRAME:
has_kf = True
break
size_cmd_has_keyframe[i] = has_kf
for i in range(lottie_tensor.seq_len.item()):
cmd_idx = int(lottie_tensor.commands[i].item())
if cmd_idx in [LottieTensor.CMD_EOS, LottieTensor.CMD_SOS, LottieTensor.CMD_PAD]:
continue
# Add command token
flattened.append(cmd_idx + COMMAND_OFFSET)
params = lottie_tensor.params[i].tolist()
if cmd_idx in TOKENIZER_COMMANDS:
# Handle tokenizer commands specially
cmd_info = TOKENIZER_COMMANDS[cmd_idx]
# 【方案C】对TOKENIZER_COMMANDS,总是写入所有regular params(包括PAD_VAL
# 原因:regular params数量少(20个),但跳过PAD_VAL会导致unflatten无法可靠解码
# 其他命令仍然动态截断以节省token
for param_idx in cmd_info['regular']:
if param_idx < len(params):
value = params[param_idx]
offset = LottieTensor.get_param_offset(cmd_idx, param_idx)
flattened.append(int(round(value)) + offset)
for count_idx, token_start, max_tokens in cmd_info['token_groups']:
actual_count = 0
for j in range(max_tokens):
if token_start + j < len(params):
token_val = int(params[token_start + j])
if token_val != LottieTensor.PAD_VAL and token_val > 0:
actual_count = j + 1
flattened.append(actual_count + NUMBER_OFFSET)
for j in range(int(max(0, actual_count))):
if token_start + j < len(params):
token_val = int(params[token_start + j])
if token_val != LottieTensor.PAD_VAL and token_val > 0:
flattened.append(token_val)
else:
# Handle regular commands
param_indices = LottieTensor.get_command_param_indices(cmd_idx)
if param_indices:
essential_count = ESSENTIAL_PARAM_COUNT.get(cmd_idx, len(param_indices))
# 【关键修复】对于SIZE类命令的特殊处理
if cmd_idx in SIZE_COMMANDS:
animated_val = params[LottieTensor.Index.Transform.ANIMATED]
# 【修复】综合判断是否是动画:
# 1. ANIMATED参数明确设为1
# 2. 或者后续有keyframe命令(前瞻检测结果)
is_animated_by_param = animated_val != LottieTensor.PAD_VAL and is_animated_value(animated_val)
is_animated_by_keyframe = size_cmd_has_keyframe.get(i, False)
is_animated = is_animated_by_param or is_animated_by_keyframe
if is_animated:
# 动画模式:只输出ANIMATED参数,值固定为1
offset = LottieTensor.get_param_offset(cmd_idx, param_indices[0])
flattened.append(1 + offset) # 固定输出整数1
continue # 跳过后续处理
# 统一使用动态截断逻辑
last_meaningful = -1
for j, param_idx in enumerate(param_indices):
if param_idx < len(params):
value = params[param_idx]
default = get_param_default(cmd_idx, j, params)
if is_meaningful_value(value, default):
last_meaningful = j
output_count = max(essential_count, last_meaningful + 1) if last_meaningful >= 0 else essential_count
output_count = min(output_count, len(param_indices))
# 写入参数
for j in range(output_count):
param_idx = param_indices[j]
if param_idx < len(params):
value = params[param_idx]
if value == LottieTensor.PAD_VAL:
default = get_param_default(cmd_idx, j, params)
value = default if default != LottieTensor.PAD_VAL else 0.0
offset = LottieTensor.get_param_offset(cmd_idx, param_idx)
flattened.append(int(round(value)) + offset)
# 智能截断逻辑
if max_length and len(flattened) > max_length:
# 如果长度在 max_length 和 2*max_length 之间,智能截断到最近的layer end
if len(flattened) <= 3 * max_length:
truncate_point = LottieTensor._find_nearest_layer_end(flattened, max_length, COMMAND_OFFSET)
if truncate_point > 0:
flattened = flattened[:truncate_point]
else:
# ❌ 找不到合适的layer end,放弃这个样本(返回None标记)
return None
else:
# 超过2倍长度,也放弃(太长了)
return None
return flattened
@staticmethod
def from_list(flattened: List[int]) -> 'LottieTensor':
"""
Reconstruct LottieTensor from a flattened 1D list.
紧凑格式解码:通过判断是否为命令Token来区分边界。
"""
# 【新增】确保tokenizer可用于文本解码
if LottieTensor.tokenizer is None:
try:
LottieTensor.init_tokenizer()
except Exception as e:
print(f"Warning: Failed to initialize tokenizer in from_list: {e}")
COMMAND_OFFSET = 151936
NUMBER_OFFSET = 173186
NUM_COMMANDS = len(LottieTensor.COMMANDS)
# SIZE类命令集合
SIZE_COMMANDS = {
LottieTensor.CMD_SIZE,
LottieTensor.CMD_ELLIPSE_SIZE,
LottieTensor.CMD_RECT_SIZE
}
# 【修复】ANIMATED 阈值常量
ANIMATED_THRESHOLD = 0.5
# Default values for parameters when not provided
PARAM_DEFAULTS = {}
# Keyframe easing defaults
#for i in [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25]:
for i in [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22]:
PARAM_DEFAULTS[(LottieTensor.CMD_KEYFRAME, i)] = 0.0
for i in range(2, 6):
PARAM_DEFAULTS[(LottieTensor.CMD_WIDTH_KEYFRAME, i)] = 0.0
for i in range(5, 9):
PARAM_DEFAULTS[(LottieTensor.CMD_COLOR_KEYFRAME, i)] = 0.0
for i in range(2, 6):
PARAM_DEFAULTS[(LottieTensor.CMD_OPACITY_KEYFRAME, i)] = 0.0
for i in range(2, 6):
PARAM_DEFAULTS[(LottieTensor.CMD_POINT, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_ANIMATION, 5)] = 0.0
for i in range(4, 13):
PARAM_DEFAULTS[(LottieTensor.CMD_LAYER, i)] = 0.0
for i in range(4, 12):
PARAM_DEFAULTS[(LottieTensor.CMD_NULL_LAYER, i)] = 0.0
for i in range(4, 15):
PARAM_DEFAULTS[(LottieTensor.CMD_PRECOMP_LAYER, i)] = 0.0
for i in range(3, 15):
PARAM_DEFAULTS[(LottieTensor.CMD_FILL, i)] = 0.0
for i in range(3, 14):
PARAM_DEFAULTS[(LottieTensor.CMD_STROKE, i)] = 0.0
for i in range(1, 5):
PARAM_DEFAULTS[(LottieTensor.CMD_GROUP, i)] = 0.0
for i in range(4, 6):
PARAM_DEFAULTS[(LottieTensor.CMD_PATH, i)] = 0.0
for i in range(8, 11):
PARAM_DEFAULTS[(LottieTensor.CMD_TRANSFORM_SHAPE, i)] = 0.0
for i in range(1, 4):
PARAM_DEFAULTS[(LottieTensor.CMD_POSITION, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_SCALE, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_ANCHOR, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_ROTATION, 1)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_OPACITY, 1)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_RECT, 1)] = 0.0
for i in range(21):
PARAM_DEFAULTS[(LottieTensor.CMD_MASK_PT_K_I, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_MASK_PT_K_O, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_MASK_PT_K_V, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_MASK_PT_KF_SHAPE_I, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_MASK_PT_KF_SHAPE_O, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_MASK_PT_KF_SHAPE_V, i)] = 0.0
for i in range(2, 6):
PARAM_DEFAULTS[(LottieTensor.CMD_DASH_KEYFRAME, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_RANGE_START_KEYFRAME, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_RANGE_END_KEYFRAME, i)] = 0.0
PARAM_DEFAULTS[(LottieTensor.CMD_RANGE_OFFSET_KEYFRAME, i)] = 0.0
TOKENIZER_COMMANDS = {
LottieTensor.CMD_FONT: {
'regular': [LottieTensor.Index.Font.ASCENT],
'token_groups': [
(LottieTensor.Index.Font.FAMILY_TOKEN_COUNT,
LottieTensor.Index.Font.FAMILY_TOKEN_0, 10),
(LottieTensor.Index.Font.STYLE_TOKEN_COUNT,
LottieTensor.Index.Font.STYLE_TOKEN_0, 10)
]
},
LottieTensor.CMD_CHAR: {
'regular': [
LottieTensor.Index.Char.SIZE,
LottieTensor.Index.Char.W
],
'token_groups': [
(LottieTensor.Index.Char.CH_TOKEN_COUNT,
LottieTensor.Index.Char.CH_TOKEN_0, 10),
(LottieTensor.Index.Char.STYLE_TOKEN_COUNT,
LottieTensor.Index.Char.STYLE_TOKEN_0, 10),
(LottieTensor.Index.Char.FAMILY_TOKEN_COUNT,
LottieTensor.Index.Char.FAMILY_TOKEN_0, 10)
]
},
LottieTensor.CMD_ASSET: {
'regular': [LottieTensor.Index.Asset.FR],
'token_groups': [
(LottieTensor.Index.Asset.ID_TOKEN_COUNT,
LottieTensor.Index.Asset.ID_TOKEN_0, 10)
]
},
LottieTensor.CMD_REFERENCE_ID: {
'regular': [],
'token_groups': [
(LottieTensor.Index.ReferenceId.ID_TOKEN_COUNT,
LottieTensor.Index.ReferenceId.ID_TOKEN_0, 10)
]
},
LottieTensor.CMD_TEXT_KEYFRAME: {
'regular': list(range(LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKENS_START)),
'token_groups': [
(LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKEN_COUNT,
LottieTensor.Index.TextKeyframe.FONT_FAMILY_TOKENS_START, 10),
(LottieTensor.Index.TextKeyframe.TEXT_TOKEN_COUNT,
LottieTensor.Index.TextKeyframe.TEXT_TOKENS_START, 15)
]
}
}
def is_command_token(token):
"""Check if a token is a command token."""
return COMMAND_OFFSET <= token < COMMAND_OFFSET + NUM_COMMANDS
def get_default_value(cmd_idx, param_pos, params):
"""Get default value for a parameter."""
# SIZE类命令的特殊处理
if cmd_idx in SIZE_COMMANDS:
animated_val = params[LottieTensor.Index.Transform.ANIMATED]
is_animated = animated_val != LottieTensor.PAD_VAL and animated_val >= ANIMATED_THRESHOLD
if param_pos in {1, 2}: # X 和 Y 参数
if is_animated:
# ANIMATED=1时,X和Y应该保持PAD_VAL(由keyframe提供)
return LottieTensor.PAD_VAL
else:
# ANIMATED=0时,X和Y默认为0
return 0.0
key = (cmd_idx, param_pos)
return PARAM_DEFAULTS.get(key, LottieTensor.PAD_VAL)
# 【新增】辅助函数:检查后续是否有keyframe命令(在遇到对应的END命令之前)
def has_following_keyframe(flattened_list, start_idx, cmd_idx):
"""检查从start_idx开始,是否有keyframe命令出现在结束标记之前"""
# SIZE命令的结束标记
SIZE_END_COMMANDS = {
LottieTensor.CMD_SIZE: LottieTensor.CMD_SIZE_END,
LottieTensor.CMD_ELLIPSE_SIZE: LottieTensor.CMD_SIZE_END,
LottieTensor.CMD_RECT_SIZE: LottieTensor.CMD_SIZE_END,
}
end_cmd = SIZE_END_COMMANDS.get(cmd_idx, LottieTensor.CMD_SIZE_END)
# 会结束 size 上下文的命令集合
context_end_cmds = {
end_cmd,
LottieTensor.CMD_FILL,
LottieTensor.CMD_STROKE,
LottieTensor.CMD_GROUP_END,
LottieTensor.CMD_ELLIPSE_END,
LottieTensor.CMD_RECT_END,
}
for k in range(start_idx, len(flattened_list)):
if is_command_token(flattened_list[k]):
cmd = flattened_list[k] - COMMAND_OFFSET
if cmd in context_end_cmds:
return False
if cmd == LottieTensor.CMD_KEYFRAME:
return True
return False
commands = []
params_list = []
i = 0
while i < len(flattened):
if is_command_token(flattened[i]):
cmd_idx = flattened[i] - COMMAND_OFFSET
commands.append(cmd_idx)
cmd_start_i = i # 记录命令的起始位置
i += 1
params = [LottieTensor.PAD_VAL] * LottieTensor.PARAM_DIM
if cmd_idx in TOKENIZER_COMMANDS:
cmd_info = TOKENIZER_COMMANDS[cmd_idx]
regular_params = cmd_info['regular']
# 【方案C】读取所有regular params - 固定长度,每个都读取
for param_idx in regular_params:
if i < len(flattened) and not is_command_token(flattened[i]):
offset = LottieTensor.get_param_offset(cmd_idx, param_idx)
params[param_idx] = float(flattened[i] - offset)
i += 1
for count_idx, token_start, max_tokens in cmd_info['token_groups']:
if i < len(flattened) and not is_command_token(flattened[i]):
count = flattened[i] - NUMBER_OFFSET
params[count_idx] = float(count)
i += 1
for j in range(int(max(0, count))):
if i < len(flattened) and not is_command_token(flattened[i]):
if token_start + j < LottieTensor.PARAM_DIM:
params[token_start + j] = float(flattened[i])
i += 1
else:
break
else:
param_indices = LottieTensor.get_command_param_indices(cmd_idx)
# Read parameters until next command
param_pos = 0
while (param_pos < len(param_indices) and
i < len(flattened) and
not is_command_token(flattened[i])):
param_idx = param_indices[param_pos]
offset = LottieTensor.get_param_offset(cmd_idx, param_idx)
params[param_idx] = float(flattened[i] - offset)
param_pos += 1
i += 1
# 【关键修复】对于SIZE类命令的特殊后处理
if cmd_idx in SIZE_COMMANDS:
animated_val = params[LottieTensor.Index.Transform.ANIMATED]
# 情况1:只读到了ANIMATED参数且值为1
if param_pos == 1 and animated_val != LottieTensor.PAD_VAL and animated_val >= ANIMATED_THRESHOLD:
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
# 情况2:没有读到有效的ANIMATED参数,通过前瞻检测判断
elif animated_val == LottieTensor.PAD_VAL or animated_val < ANIMATED_THRESHOLD:
# 使用修改后的函数,传入命令类型
if has_following_keyframe(flattened, i, cmd_idx):
params[LottieTensor.Index.Transform.ANIMATED] = 1.0
elif param_pos >= 2:
# 读到了多个参数,是静态 size
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# 情况3:读到了多个参数(静态size),确保ANIMATED=0
elif param_pos >= 2 and (animated_val == LottieTensor.PAD_VAL or animated_val < ANIMATED_THRESHOLD):
params[LottieTensor.Index.Transform.ANIMATED] = 0.0
# Fill in defaults for remaining parameters
for j in range(param_pos, len(param_indices)):
param_idx = param_indices[j]
# 获取默认值,需要传入当前params来判断ANIMATED状态
default_val = get_default_value(cmd_idx, j, params)
if default_val != LottieTensor.PAD_VAL:
params[param_idx] = default_val
params_list.append(params)
else:
i += 1
if commands:
commands_tensor = torch.tensor(commands).reshape(-1, 1).long()
params_tensor = torch.tensor(params_list).float()
else:
commands_tensor = torch.zeros((0, 1)).long()
params_tensor = torch.zeros((0, LottieTensor.PARAM_DIM)).float()
return LottieTensor(commands_tensor, params_tensor)