def gen_effects_opacity(lottie, layer, idx): """ Generates the dictionary corresponding to effects/opacity.json Args: lottie (dict) : Lottie format effects stored in this layer (lxml.etree._Element) : Synfig format layer idx (int) : Index/Count of effect Returns: (None) """ index = Count() lottie["ty"] = settings.EFFECTS_OPACITY # Effect type lottie["nm"] = "Opacity" # Name lottie["ix"] = idx # Index lottie["v"] = {} # Value of opacity for child in layer: if child.attrib["name"] == "amount": is_animate = is_animated(child[0]) if is_animate == 2: # Telling the function that this is for opacity child[0].attrib['type'] = 'effects_opacity' gen_value_Keyframed(lottie["v"], child[0], index.inc()) else: if is_animate == 0: val = float(child[0].attrib["value"]) else: val = float(child[0][0][0].attrib["value"]) gen_properties_value(lottie["v"], val, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO)
def parse(file_name): """ Driver function for parsing .sif to lottie(.json) format Args: file_name (str) : Synfig file name that needs to be parsed to Lottie format Returns: (str) : File name in json format """ tree = etree.parse(file_name) root = tree.getroot() # canvas gen_canvas(settings.lottie_format, root) # Storing the file name settings.file_name["fn"] = file_name # Storing the file directory settings.file_name["fd"] = os.path.dirname(file_name) num_layers = Count() settings.lottie_format["layers"] = [] shape_layer = {"star", "circle", "rectangle", "simple_circle"} solid_layer = {"SolidColor"} image_layer = {"import"} supported_layers = shape_layer.union(solid_layer) supported_layers = supported_layers.union(image_layer) for child in root: if child.tag == "layer": if child.attrib["active"] == "false": # Only render the active layers continue if child.attrib["type"] not in supported_layers: # Only supported layers continue settings.lottie_format["layers"].insert(0, {}) if child.attrib["type"] in shape_layer: # Goto shape layer gen_layer_shape(settings.lottie_format["layers"][0], child, num_layers.inc()) elif child.attrib["type"] in solid_layer: # Goto solid layer gen_layer_solid(settings.lottie_format["layers"][0], child, num_layers.inc()) elif child.attrib["type"] in image_layer: gen_layer_image(settings.lottie_format["layers"][0], child, num_layers.inc()) lottie_string = json.dumps(settings.lottie_format) return write_to(file_name, "json", lottie_string)
def gen_effects_vfeather(lottie, layer, idx): """ Generates the dictionary corresponding to effects/vertical feather Args: lottie (dict) : Lottie format effects stored in this layer (lxml.etree._Element) : Synfig format layer idx (int) : Index/Count of effect Returns: (None) """ index = Count() lottie["ty"] = settings.EFFECTS_VFEATHER # Effect type lottie["nm"] = "Vertical Feather" # Name lottie["ix"] = idx # Index lottie["v"] = {} # value gen_properties_value(lottie["v"], 0, index.inc(), 0, settings.NO_INFO)
def gen_effects_fillmask(lottie, layer, idx): """ Generates the dictionary corresponding to effects/fillmask.json Args: lottie (dict) : Lottie format effects stored in this layer (lxml.etree._Element) : Synfig format layer idx (int) : Index/Count of effect Returns: (None) """ index = Count() lottie["ty"] = settings.EFFECTS_FILL_MASK # Effect type lottie["nm"] = "Fill Mask" # Name lottie["ix"] = idx # Index lottie["v"] = {} # value gen_properties_value(lottie["v"], 0, index.inc(), 0, settings.NO_INFO)
def gen_layer_shape(lottie, layer, idx): """ Generates the dictionary corresponding to layers/shape.json Args: lottie (dict) : Lottie generate shape stored here layer (lxml.etree._Element): Synfig format shape layer idx (int) : Stores the index(number of) of shape layer Returns: (None) """ index = Count() lottie["ddd"] = settings.DEFAULT_3D lottie["ind"] = idx lottie["ty"] = settings.LAYER_SHAPE_TYPE lottie["nm"] = settings.LAYER_SHAPE_NAME + str(idx) lottie["sr"] = settings.LAYER_DEFAULT_STRETCH lottie["ks"] = {} # Transform properties to be filled pos = [0, 0] # default anchor = [0, 0, 0] # default scale = [100, 100, 100] # default gen_helpers_transform(lottie["ks"], layer, pos, anchor, scale) lottie["ao"] = settings.LAYER_DEFAULT_AUTO_ORIENT lottie["shapes"] = [] # Shapes to be filled yet lottie["shapes"].append({}) if layer.attrib["type"] == "star": gen_shapes_star(lottie["shapes"][0], layer, index.inc()) elif layer.attrib["type"] in {"circle", "simple_circle"}: gen_shapes_circle(lottie["shapes"][0], layer, index.inc()) elif layer.attrib["type"] == "rectangle": gen_shapes_rectangle(lottie["shapes"][0], layer, index.inc()) lottie["shapes"].append({}) # For the fill or color gen_shapes_fill(lottie["shapes"][1], layer) lottie["ip"] = settings.lottie_format["ip"] lottie["op"] = settings.lottie_format["op"] lottie["st"] = 0 # Don't know yet get_blend(lottie, layer) lottie["markers"] = [] # Markers to be filled yet
def gen_layer_solid(lottie, layer, idx): """ Generates the dictionary corresponding to layers/solid.json Args: lottie (dict) : Lottie generated solid layer stored here layer (lxml.etree._Element): Synfig format solid layer idx (int) : Stores the index(number of) of solid layer Returns: (None) """ index = Count() lottie["ddd"] = settings.DEFAULT_3D lottie["ind"] = idx lottie["ty"] = settings.LAYER_SOLID_TYPE lottie["nm"] = settings.LAYER_SOLID_NAME + str(idx) lottie["sr"] = settings.LAYER_DEFAULT_STRETCH lottie["ks"] = {} # Transform properties to be filled lottie["ef"] = [] # Stores the effects pos = [settings.lottie_format["w"] / 2, settings.lottie_format["h"] / 2] anchor = pos gen_helpers_transform(lottie["ks"], layer, pos, anchor) lottie["ef"].append({}) gen_effects_fill(lottie["ef"][-1], layer, index.inc()) lottie["ao"] = settings.LAYER_DEFAULT_AUTO_ORIENT lottie["sw"] = settings.lottie_format["w"] # Solid Width lottie["sh"] = settings.lottie_format["h"] # Solid Height for chld in layer: if chld.tag == "param": if chld.attrib["name"] == "color": lottie["sc"] = get_color_hex(chld[0]) # Solid Color lottie["ip"] = settings.lottie_format["ip"] lottie["op"] = settings.lottie_format["op"] lottie["st"] = 0 # Don't know yet get_blend(lottie, layer) lottie["markers"] = [] # Markers to be filled yet
def gen_effects_color(lottie, layer, idx): """ Generates the dictionary corresponding to effects/color.json Args: lottie (dict) : Lottie format effects stored in this layer (lxml.etree._Element) : Synfig format layer idx (int) : Index/Count of effect Returns: (None) """ index = Count() lottie["ty"] = settings.EFFECTS_COLOR # Effect type lottie["nm"] = "Color" # Name lottie["ix"] = idx # Index lottie["v"] = {} # Value of color for child in layer: if child.tag == "param": if child.attrib["name"] == "color": is_animate = is_animated(child[0]) if is_animate == 2: gen_value_Keyframed(lottie["v"], child[0], index.inc()) else: if is_animate == 0: val = child[0] else: val = child[0][0][0] red = float(val[0].text) green = float(val[1].text) blue = float(val[2].text) red, green, blue = red ** (1/settings.GAMMA), green **\ (1/settings.GAMMA), blue ** (1/ settings.GAMMA) alpha = float(val[3].text) gen_properties_value(lottie["v"], [red, green, blue, alpha], index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO)
def gen_effects_fill(lottie, layer, idx): """ Generates the dictionary corresponding to effects/fill.json Args: lottie (dict) : Lottie format layer layer (lxml.etree._Element) : Synfig format layer idx (int) : Index/Count of effect Returns: (None) """ index = Count() lottie["ty"] = settings.EFFECTS_FILL # Effect type lottie["nm"] = "Fill" # Name lottie["ix"] = idx # Index lottie["ef"] = [] # Effect list of properties # generating the fill mask, has no use in Synfig. But a necessity for # running the .json file lottie["ef"].append({}) gen_effects_fillmask(lottie["ef"][-1], layer, index.inc()) # generating the all mask property as required by lottie lottie["ef"].append({}) gen_effects_allmask(lottie["ef"][-1], layer, index.inc()) # generating the color property lottie["ef"].append({}) gen_effects_color(lottie["ef"][-1], layer, index.inc()) # generating the invert property as required by lottie lottie["ef"].append({}) gen_effects_invert(lottie["ef"][-1], layer, index.inc()) # generating the horizontal feather as required by lottie lottie["ef"].append({}) gen_effects_hfeather(lottie["ef"][-1], layer, index.inc()) # generating the vertical feather as required by lottie lottie["ef"].append({}) gen_effects_vfeather(lottie["ef"][-1], layer, index.inc()) # generating the opacity lottie["ef"].append({}) gen_effects_opacity(lottie["ef"][-1], layer, index.inc())
def gen_layer_image(lottie, layer, idx): """ Generates the dictionary corresponding to layers/image.json Args: lottie (dict) : Lottie generated image stored here layer (lxml.etree._Element): Synfig format image layer idx (int) : Stores the index(number of) of image layer Returns: (None) """ index = Count() lottie["ddd"] = settings.DEFAULT_3D lottie["ind"] = idx lottie["ty"] = settings.LAYER_IMAGE_TYPE lottie["nm"] = settings.LAYER_IMAGE_NAME + str(idx) lottie["sr"] = settings.LAYER_DEFAULT_STRETCH lottie["ks"] = {} # Transform properties to be filled settings.lottie_format["assets"].append({}) st = add_image_asset(settings.lottie_format["assets"][-1], layer) asset = settings.lottie_format["assets"][-1] # setting class (jpg, png) lottie["cl"] = asset["p"].split(".")[-1] # setting the reference id lottie["refId"] = asset["id"] pos1_animate = is_animated(st["tl"][0]) pos2_animate = is_animated(st["br"][0]) # If pos1 is not animated if pos1_animate in {0, 1}: st["tl"] = gen_dummy_waypoint(st["tl"], pos1_animate, "vector") # If pos2 is not animated if pos2_animate in {0, 1}: st["br"] = gen_dummy_waypoint(st["br"], pos2_animate, "vector") st["scale"] = gen_image_scale(st["tl"][0], st["br"][0], asset["w"], asset["h"]) anchor = [0, 0, 0] gen_helpers_transform(lottie["ks"], layer, st["tl"][0], anchor, st["scale"][0]) lottie["ao"] = settings.LAYER_DEFAULT_AUTO_ORIENT lottie["ip"] = settings.lottie_format["ip"] lottie["op"] = settings.lottie_format["op"] lottie["st"] = 0 # Don't know yet get_blend(lottie, layer) lottie["markers"] = [] # Markers to be filled yet
def gen_shapes_fill(lottie, layer): """ Generates the dictionary corresponding to shapes/fill.json Args: lottie (dict) : The lottie generated fill layer will be stored in it layer (lxml.etree._Element): Synfig format fill (can be shape/solid anything, we only need color and opacity part from it) layer Returns: (None) """ index = Count() lottie["ty"] = "fl" # Type if fill lottie["c"] = {} # Color lottie["o"] = {} # Opacity of the fill layer for child in layer: if child.tag == "param": if child.attrib["name"] == "color": is_animate = is_animated(child[0]) if is_animate == 2: gen_value_Keyframed(lottie["c"], child[0], index.inc()) else: if is_animate == 0: val = child[0] else: val = child[0][0][0] red = float(val[0].text) green = float(val[1].text) blue = float(val[2].text) red, green, blue = red ** (1/settings.GAMMA), green **\ (1/settings.GAMMA), blue ** (1/ settings.GAMMA) alpha = float(val[3].text) gen_properties_value(lottie["c"], [red, green, blue, alpha], index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) elif child.attrib["name"] == "amount": is_animate = is_animated(child[0]) if is_animate == 2: # Telling the function that this is for opacity child[0].attrib['type'] = 'opacity' gen_value_Keyframed(lottie["o"], child[0], index.inc()) else: if is_animate == 0: val = float(child[0].attrib["value"] ) * settings.OPACITY_CONSTANT else: val = float(child[0][0][0].attrib["value"] ) * settings.OPACITY_CONSTANT gen_properties_value(lottie["o"], val, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO)
def init(): """ Initialises the final dictionary corresponding to conversion and also the canvas dictionary needed in misc functions Args: (None) Returns: (None) """ # Final converted dictionary global lottie_format lottie_format = {} global view_box_canvas view_box_canvas = {} global num_images num_images = Count() global file_name file_name = {}
def gen_shapes_star(lottie, layer, idx): """ Generates the dictionary corresponding to shapes/star.json Args: lottie (dict) : The lottie generated star layer will be stored in it layer (lxml.etree._Element): Synfig format star layer idx (int) : Stores the index of the star layer Returns: (None) """ index = Count() lottie["ty"] = "sr" # Type: star lottie["pt"] = {} # Number of points on the star lottie["p"] = {} # Position of star lottie["r"] = {} # Angle / Star's rotation lottie["ir"] = {} # Inner radius lottie["or"] = {} # Outer radius lottie["is"] = {} # Inner roundness of the star lottie["os"] = {} # Outer roundness of the star regular_polygon = {"prop": "false"} for child in layer: if child.tag == "param": if child.attrib["name"] == "regular_polygon": is_animate = is_animated(child[0]) if is_animate == 2: regular_polygon["prop"] = "changing" # Copy the child address to dictionary regular_polygon["animated"] = child[0] elif is_animate == 1: regular_polygon["prop"] = child[0][0][0].attrib["value"] else: regular_polygon["prop"] = child[0].attrib["value"] regular_polygon["animate"] = is_animate elif child.attrib["name"] == "points": is_animate = is_animated(child[0]) if is_animate == 2: # To uniquely identify the points, attribute type is changed child[0].attrib['type'] = 'points' gen_value_Keyframed(lottie["pt"], child[0], index.inc()) else: num_points = 3 # default number of points if is_animate == 0: num_points = int(child[0].attrib["value"]) else: num_points = int(child[0][0][0].attrib["value"]) gen_properties_value(lottie["pt"], num_points, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) elif child.attrib["name"] == "angle": is_animate = is_animated(child[0]) if is_animate == 2: gen_value_Keyframed(lottie["r"], child[0], index.inc()) else: theta = 0 # default angle for the star if is_animate == 0: theta = get_angle(float(child[0].attrib["value"])) else: theta = get_angle(float( child[0][0][0].attrib["value"])) gen_properties_value(lottie["r"], theta, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) elif child.attrib["name"] == "radius1": is_animate = is_animated(child[0]) if is_animate == 2: gen_value_Keyframed(lottie["or"], child[0], index.inc()) else: r_outer = 0 # default value for outer radius if is_animate == 0: r_outer = float(child[0].attrib["value"]) else: r_outer = float(child[0][0][0].attrib["value"]) gen_properties_value(lottie["or"], int(settings.PIX_PER_UNIT * r_outer), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) elif child.attrib["name"] == "radius2": is_animate = is_animated(child[0]) if is_animate == 2: gen_value_Keyframed(lottie["ir"], child[0], index.inc()) else: r_inner = 0 # default value for inner radius if is_animate == 0: r_inner = float(child[0].attrib["value"]) else: r_inner = float(child[0][0][0].attrib["value"]) gen_properties_value(lottie["ir"], int(settings.PIX_PER_UNIT * r_inner), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) elif child.attrib["name"] == "origin": is_animate = is_animated(child[0]) if is_animate == 2: gen_properties_multi_dimensional_keyframed( lottie["p"], child[0], index.inc()) else: x_val, y_val = 0, 0 if is_animate == 0: x_val = float(child[0][0].text) * settings.PIX_PER_UNIT y_val = float(child[0][1].text) * settings.PIX_PER_UNIT else: x_val = float( child[0][0][0][0].text) * settings.PIX_PER_UNIT y_val = float( child[0][0][0][1].text) * settings.PIX_PER_UNIT gen_properties_value(lottie["p"], change_axis(x_val, y_val), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # If not animated, then go to if, else if regular_polygon["animate"] in {0, 1}: if regular_polygon["prop"] == "false": lottie["sy"] = 1 # Star Type # inner property is only needed if type is star gen_properties_value(lottie["is"], 0, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) else: lottie["sy"] = 2 # Polygon Type # for polygon type, "ir" and "is" must not be present del lottie["ir"] # If animated, it will always be of type star else: polygon_correction(lottie, regular_polygon["animated"]) lottie["sy"] = 1 gen_properties_value(lottie["is"], 0, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) gen_properties_value(lottie["os"], 0, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) lottie["ix"] = idx
def gen_shapes_rectangle(lottie, layer, idx): """ Generates the dictionary corresponding to shapes/rect.json Args: lottie (dict) : The lottie generated rectangle layer will be stored in it layer (lxml.etree._Element): Synfig format rectangle layer idx (int) : Stores the index of the rectangle layer Returns: (None) """ index = Count() lottie["ty"] = "rc" # Type: rectangle lottie["p"] = {} # Position of rectangle lottie["d"] = settings.DEFAULT_DIRECTION lottie["s"] = {} # Size of rectangle lottie["ix"] = idx # setting the index lottie["r"] = {} # Rounded corners of rectangle points = {} for child in layer: if child.tag == "param": if child.attrib["name"] == "point1": points["1"] = child # Store address of child here elif child.attrib["name"] == "point2": points["2"] = child # Store address of child here elif child.attrib["name"] == "expand": expand_animate = is_animated(child[0]) param_expand = child elif child.attrib["name"] == "bevel": is_animate = is_animated(child[0]) if is_animate == 2: gen_value_Keyframed(lottie["r"], child[0], index.inc()) else: bevel = get_child_value(is_animate, child, "value") bevel *= settings.PIX_PER_UNIT gen_properties_value(lottie["r"], bevel, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) p1_animate = is_animated(points["1"][0]) p2_animate = is_animated(points["2"][0]) # If expand parameter is not animated if expand_animate in {0, 1}: param_expand = gen_dummy_waypoint(param_expand, expand_animate, "real") # p1 not animated and p2 not animated if p1_animate in {0, 1} and p2_animate in {0, 1}: points["1"] = gen_dummy_waypoint(points["1"], p1_animate, "vector") points["2"] = gen_dummy_waypoint(points["2"], p2_animate, "vector") # p1 is animated and p2 is not animated elif p1_animate == 2 and p2_animate in {0, 1}: points["2"] = gen_dummy_waypoint(points["2"], p2_animate, "vector") # p1 is not animated and p2 is animated elif p1_animate in {0, 1} and p2_animate == 2: points["1"] = gen_dummy_waypoint(points["1"], p1_animate, "vector") both_points_animated(points["1"], points["2"], param_expand, lottie, index)
def gen_helpers_transform(lottie, layer, pos=[0, 0], anchor=[0, 0, 0], scale=[100, 100, 100]): """ Generates the dictionary corresponding to helpers/transform.json Args: lottie (dict) : Lottie format layer layer (lxml.etree._Element) : Synfig format layer pos (:obj: `list | lxml.etree._Element`, optional) : position of layer anchor (:obj: `list`) : anchor point of layer scale (:obj: `list | lxml.etree._Element`, optional) : scale of layer Returns: (None) """ index = Count() lottie["o"] = {} # opacity/Amount lottie["r"] = {} # Rotation of the layer lottie["p"] = {} # Position of the layer lottie["a"] = {} # Anchor point of the layer lottie["s"] = {} # Scale of the layer # setting the default location if isinstance(pos, list): gen_properties_value(lottie["p"], pos, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) else: gen_properties_multi_dimensional_keyframed(lottie["p"], pos, index.inc()) # setting the default opacity i.e. 100 gen_properties_value(lottie["o"], settings.DEFAULT_OPACITY, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # setting the rotation gen_properties_value(lottie["r"], settings.DEFAULT_ROTATION, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # setting the anchor point gen_properties_value(lottie["a"], anchor, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # setting the scale if isinstance(scale, list): gen_properties_value(lottie["s"], scale, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # This means scale parameter is animated else: gen_value_Keyframed(lottie["s"], scale, index.inc())