Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
312e285e47 | ||
|
|
385ee20f78 |
@@ -117,6 +117,8 @@ Using flux klein edit(to make new objects appear) + wan video.
|
||||
|
||||
## Changes
|
||||
|
||||
* v1.0.9: Fixed up warning message for nodes inside the image input / output nodes.
|
||||
* v1.0.8: Add column node. Adds a column, repeats values until it fills the source spreadsheet's rows
|
||||
* v1.0.7: Reverted changes for errors/warnings when running. It wasn't working for KJ get/set nodes.
|
||||
* v1.0.6: Fix missing first image in video. Remove checking errors/warnings when running.
|
||||
* v1.0.5: File list node now uses the input folder as source. Don't crash when numbers in the columns are not correct.
|
||||
|
||||
+216
-110
@@ -6,6 +6,8 @@ from server import PromptServer
|
||||
import sys
|
||||
import csv
|
||||
import hashlib
|
||||
import base64
|
||||
import pickle
|
||||
from io import StringIO
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
@@ -248,6 +250,7 @@ class GroupInfo():
|
||||
self.group_name = group_name
|
||||
self.first_input_node_id = first_input_node_id
|
||||
self.output_image_link = None
|
||||
self.restored_nodes = {}
|
||||
|
||||
|
||||
def change_out_node(self, graph):
|
||||
@@ -265,111 +268,6 @@ class GroupInfo():
|
||||
# logging.warning("S2V: Could not find output image node")
|
||||
return found_node
|
||||
|
||||
@classmethod
|
||||
def map_children(cls, prompt):
|
||||
input_node_ids = []
|
||||
child_node_ids = {}
|
||||
for node_id, node in prompt.items():
|
||||
if "inputs" not in node:
|
||||
continue
|
||||
|
||||
for k, v in node["inputs"].items():
|
||||
if not is_link(v):
|
||||
continue
|
||||
|
||||
parent_node_id = v[0]
|
||||
if parent_node_id not in child_node_ids:
|
||||
child_node_ids[parent_node_id] = {"in":[], "out":[]}
|
||||
child_node_ids[parent_node_id]["out"].append(node_id)
|
||||
|
||||
if node_id not in child_node_ids:
|
||||
child_node_ids[node_id] = {"in":[], "out":[]}
|
||||
child_node_ids[node_id]["in"].append(parent_node_id)
|
||||
|
||||
if node["class_type"] == "Spreadsheet2VideoInputImage":
|
||||
input_node_ids.append(node_id)
|
||||
|
||||
return (child_node_ids,input_node_ids)
|
||||
|
||||
@classmethod
|
||||
def add_map_output_by_group_name(cls, group_name_by_output_id, child_node_ids, children, group_name):
|
||||
for child_id in children["out"]:
|
||||
if child_id in group_name_by_output_id:
|
||||
existing_group_name = group_name_by_output_id[child_id]
|
||||
if existing_group_name != group_name:
|
||||
raise ValueError(f"S2V: Node is in both column1 groups, node id:{child_id}, names:{existing_group_name}, {group_name}")
|
||||
|
||||
group_name_by_output_id[child_id] = group_name
|
||||
if child_id not in child_node_ids:
|
||||
logging.error(f"S2V: Cannot find output node id: {child_id}")
|
||||
continue
|
||||
cls.add_map_output_by_group_name(
|
||||
group_name_by_output_id,
|
||||
child_node_ids,
|
||||
child_node_ids[child_id],
|
||||
group_name
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def map_output_by_group_name(cls, group_name_by_output_id, prompt):
|
||||
(child_node_ids, input_node_ids) = cls.map_children(prompt)
|
||||
|
||||
for input_node_id in input_node_ids:
|
||||
inputImageNode = prompt[input_node_id]
|
||||
group_name = cls.get_group_name_from_node(inputImageNode)
|
||||
|
||||
group_name_by_output_id[input_node_id] = group_name
|
||||
cls.add_map_output_by_group_name(
|
||||
group_name_by_output_id,
|
||||
child_node_ids,
|
||||
child_node_ids[input_node_id],
|
||||
group_name
|
||||
)
|
||||
|
||||
return group_name_by_output_id
|
||||
|
||||
@classmethod
|
||||
def get_group_name_from_node(cls, inputImageNode):
|
||||
if inputImageNode is None or "inputs" not in inputImageNode:
|
||||
return None
|
||||
|
||||
group_name = inputImageNode["inputs"]["COLUMN1"].strip()
|
||||
if group_name == "":
|
||||
return None
|
||||
|
||||
return group_name
|
||||
|
||||
@classmethod
|
||||
def map_outputs(cls, prompt):
|
||||
(child_node_ids, input_node_ids) = cls.map_children(prompt)
|
||||
|
||||
group_name_by_output_id = {}
|
||||
cls.map_output_by_group_name(group_name_by_output_id, prompt)
|
||||
|
||||
by_group_name = {}
|
||||
for input_node_id in input_node_ids:
|
||||
inputImageNode = prompt[input_node_id]
|
||||
|
||||
group_name = cls.get_group_name_from_node(inputImageNode)
|
||||
if group_name is None:
|
||||
continue
|
||||
|
||||
if group_name in by_group_name:
|
||||
raise ValueError(f"S2V: Duplicate group names: {group_name}")
|
||||
|
||||
group_info = GroupInfo(input_node_id, group_name)
|
||||
by_group_name[group_name] = group_info
|
||||
|
||||
group_info.add_orig_node_ids(
|
||||
prompt,
|
||||
child_node_ids[input_node_id],
|
||||
child_node_ids,
|
||||
group_name_by_output_id,
|
||||
0
|
||||
)
|
||||
logging.info(f"S2V: group: {group_name}, nodes: {group_info.get_orig_node_ids_count()}")
|
||||
|
||||
return by_group_name
|
||||
|
||||
def add_orig_node_ids(
|
||||
self,
|
||||
@@ -377,10 +275,12 @@ class GroupInfo():
|
||||
children,
|
||||
child_node_ids,
|
||||
group_name_by_output_id,
|
||||
parent_is_out,
|
||||
depth
|
||||
):
|
||||
for child_id in (children["in"] + children["out"]):
|
||||
for (child_id, node_is_out) in ([ [i,False] for i in children["in"]] + [ [i,True] for i in children["out"]]):
|
||||
must_be_group = group_name_by_output_id[child_id] if child_id in group_name_by_output_id else None
|
||||
is_out = parent_is_out and node_is_out
|
||||
|
||||
class_type = prompt[child_id]["class_type"]
|
||||
if (
|
||||
@@ -391,8 +291,8 @@ class GroupInfo():
|
||||
(must_be_group is not None and must_be_group != self.group_name)
|
||||
):
|
||||
pass
|
||||
elif (child_id not in self.orig_node_ids):
|
||||
self.orig_node_ids[child_id] = True
|
||||
elif(child_id not in self.orig_node_ids):
|
||||
self.orig_node_ids[child_id] = is_out
|
||||
|
||||
if child_id in child_node_ids:
|
||||
self.add_orig_node_ids(
|
||||
@@ -400,8 +300,13 @@ class GroupInfo():
|
||||
child_node_ids[child_id],
|
||||
child_node_ids,
|
||||
group_name_by_output_id,
|
||||
is_out,
|
||||
depth+1
|
||||
)
|
||||
elif(is_out is True):
|
||||
# if the node is on the output path ever. It is out.
|
||||
# Cause the path is not processed in any order.
|
||||
self.orig_node_ids[child_id] = is_out
|
||||
|
||||
def get_orig_node_ids_count(self):
|
||||
return len(self.orig_node_ids.keys())
|
||||
@@ -411,7 +316,11 @@ class GroupInfo():
|
||||
|
||||
node_id_map = {}
|
||||
for node_id in contained:
|
||||
original_node = prompt[node_id]
|
||||
if node_id in prompt:
|
||||
original_node = prompt[node_id]
|
||||
else:
|
||||
original_node = self.restored_nodes[node_id]
|
||||
|
||||
new_node = graph.node(original_node["class_type"])
|
||||
new_node.set_override_display_id(node_id)
|
||||
node_id_map[node_id] = (original_node, new_node)
|
||||
@@ -506,6 +415,183 @@ class GroupInfo():
|
||||
# node['class_type']="Spreadsheet2VideoDoNothing"
|
||||
# node['inputs']={}
|
||||
|
||||
@classmethod
|
||||
def restore_group_infos_from_prompt(cls, prompt):
|
||||
by_group_name = {}
|
||||
for node_id, node in prompt.items():
|
||||
if "_meta" not in node:
|
||||
continue
|
||||
|
||||
meta = node["_meta"]
|
||||
if "__group_info" in meta:
|
||||
group_info = pickle.loads(base64.b64decode(meta["__group_info"]))
|
||||
if "__removed" in meta:
|
||||
group_info.restored_nodes = meta["__removed"]
|
||||
by_group_name[group_info.group_name] = group_info
|
||||
if(len(by_group_name.keys()) == 0):
|
||||
return None
|
||||
return by_group_name
|
||||
|
||||
def remove_nodes_from_prompt(self, prompt, exclude_ids):
|
||||
removed = {}
|
||||
|
||||
for node_id, is_out in self.orig_node_ids.items():
|
||||
if node_id in prompt and node_id not in exclude_ids:
|
||||
removed[node_id]= prompt[node_id]
|
||||
del prompt[node_id]
|
||||
return removed
|
||||
|
||||
@classmethod
|
||||
def remove_nodes_in_group_infos(cls, prompt, by_group_name, exclude_ids):
|
||||
for name, group_info in by_group_name.items():
|
||||
input_node = prompt[group_info.first_input_node_id]
|
||||
|
||||
raw_bytes = pickle.dumps(group_info)
|
||||
# base64 encodes the binary data into a clean, portable text string
|
||||
if "_meta" not in input_node:
|
||||
input_node["_meta"] = {}
|
||||
input_node["_meta"]["__group_info"] = base64.b64encode(raw_bytes).decode("utf-8")
|
||||
|
||||
removed = group_info.remove_nodes_from_prompt(prompt, exclude_ids)
|
||||
input_node["_meta"]["__removed"] = removed
|
||||
|
||||
|
||||
class MapOutputs():
|
||||
def __init__(self):
|
||||
self.child_node_ids = {}
|
||||
self.input_node_ids = []
|
||||
|
||||
def map_children(self, prompt):
|
||||
input_node_ids = []
|
||||
child_node_ids = {}
|
||||
for node_id, node in prompt.items():
|
||||
if "inputs" not in node or "class_type" not in node:
|
||||
continue
|
||||
|
||||
for k, v in node["inputs"].items():
|
||||
if not is_link(v):
|
||||
continue
|
||||
|
||||
parent_node_id = v[0]
|
||||
if parent_node_id not in child_node_ids:
|
||||
child_node_ids[parent_node_id] = {"in":[], "out":[]}
|
||||
child_node_ids[parent_node_id]["out"].append(node_id)
|
||||
|
||||
if node_id not in child_node_ids:
|
||||
child_node_ids[node_id] = {"in":[], "out":[]}
|
||||
child_node_ids[node_id]["in"].append(parent_node_id)
|
||||
|
||||
if node["class_type"] == "Spreadsheet2VideoInputImage":
|
||||
input_node_ids.append(node_id)
|
||||
|
||||
self.child_node_ids = child_node_ids
|
||||
self.input_node_ids = input_node_ids
|
||||
return (child_node_ids,input_node_ids)
|
||||
|
||||
def get_group_name_from_node(self, inputImageNode):
|
||||
if inputImageNode is None or "inputs" not in inputImageNode:
|
||||
return None
|
||||
|
||||
group_name = inputImageNode["inputs"]["COLUMN1"].strip()
|
||||
if group_name == "":
|
||||
return None
|
||||
|
||||
return group_name
|
||||
|
||||
def add_map_output_by_group_name(self, group_name_by_output_id, child_node_ids, children, group_name):
|
||||
for child_id in children["out"]:
|
||||
if child_id in group_name_by_output_id:
|
||||
existing_group_name = group_name_by_output_id[child_id]
|
||||
if existing_group_name != group_name:
|
||||
raise ValueError(f"S2V: Node is in both column1 groups, node id:{child_id}, names:{existing_group_name}, {group_name}")
|
||||
|
||||
group_name_by_output_id[child_id] = group_name
|
||||
if child_id not in child_node_ids:
|
||||
logging.error(f"S2V: Cannot find output node id: {child_id}")
|
||||
continue
|
||||
self.add_map_output_by_group_name(
|
||||
group_name_by_output_id,
|
||||
child_node_ids,
|
||||
child_node_ids[child_id],
|
||||
group_name
|
||||
)
|
||||
|
||||
def map_output_by_group_name(self, group_name_by_output_id, prompt):
|
||||
(child_node_ids, input_node_ids) = self.map_children(prompt)
|
||||
|
||||
for input_node_id in input_node_ids:
|
||||
inputImageNode = prompt[input_node_id]
|
||||
group_name = self.get_group_name_from_node(inputImageNode)
|
||||
|
||||
group_name_by_output_id[input_node_id] = group_name
|
||||
self.add_map_output_by_group_name(
|
||||
group_name_by_output_id,
|
||||
child_node_ids,
|
||||
child_node_ids[input_node_id],
|
||||
group_name
|
||||
)
|
||||
|
||||
return group_name_by_output_id
|
||||
|
||||
def map_outputs(self, prompt):
|
||||
(child_node_ids, input_node_ids) = self.map_children(prompt)
|
||||
|
||||
group_name_by_output_id = {}
|
||||
self.map_output_by_group_name(group_name_by_output_id, prompt)
|
||||
|
||||
by_group_name = {}
|
||||
for input_node_id in input_node_ids:
|
||||
inputImageNode = prompt[input_node_id]
|
||||
|
||||
group_name = self.get_group_name_from_node(inputImageNode)
|
||||
if group_name is None:
|
||||
continue
|
||||
|
||||
if group_name in by_group_name:
|
||||
raise ValueError(f"S2V: Duplicate group names: {group_name}")
|
||||
|
||||
group_info = GroupInfo(input_node_id, group_name)
|
||||
by_group_name[group_name] = group_info
|
||||
|
||||
group_info.add_orig_node_ids(
|
||||
prompt,
|
||||
child_node_ids[input_node_id],
|
||||
child_node_ids,
|
||||
group_name_by_output_id,
|
||||
True,
|
||||
0
|
||||
)
|
||||
logging.info(f"S2V: group: {group_name}, nodes: {group_info.get_orig_node_ids_count()}")
|
||||
|
||||
return by_group_name
|
||||
|
||||
|
||||
def mark_parent_nodes(self, mark_dict, node):
|
||||
for parent_id in node["in"]:
|
||||
if parent_id in mark_dict:
|
||||
continue
|
||||
mark_dict[parent_id] = True
|
||||
for node_id in self.child_node_ids:
|
||||
node = self.child_node_ids[node_id]
|
||||
self.mark_parent_nodes(mark_dict, node)
|
||||
|
||||
def nodes_used_by_other_nodes(self, prompt, by_group_name) -> dict[str, bool]:
|
||||
used_by_s2v_ids : dict[str, bool] = {}
|
||||
not_used_by_s2v_ids : dict[str, bool] = {}
|
||||
used_by_other_nodes : dict[str, bool] = {}
|
||||
|
||||
for name, group_info in by_group_name.items():
|
||||
for node_id in group_info.orig_node_ids.keys():
|
||||
used_by_s2v_ids[node_id] = True
|
||||
|
||||
not_used_by_s2v_ids = { k: True for k in prompt.keys() if k not in used_by_s2v_ids }
|
||||
for node_id in not_used_by_s2v_ids:
|
||||
for node_id in self.child_node_ids:
|
||||
node = self.child_node_ids[node_id]
|
||||
self.mark_parent_nodes(used_by_other_nodes, node)
|
||||
return used_by_other_nodes
|
||||
|
||||
|
||||
|
||||
class Spreadsheet2VideoFinalVideo(io.ComfyNode):
|
||||
@classmethod
|
||||
@@ -928,7 +1014,9 @@ class Spreadsheet2VideoNode(io.ComfyNode):
|
||||
# Use StringIO to treat the string as a file
|
||||
f = StringIO(spreadsheet)
|
||||
|
||||
by_group_name = GroupInfo.map_outputs(cls.hidden.prompt)
|
||||
by_group_name = GroupInfo.restore_group_infos_from_prompt(cls.hidden.prompt)
|
||||
if by_group_name is None:
|
||||
by_group_name = MapOutputs().map_outputs(cls.hidden.prompt)
|
||||
used_groups = {}
|
||||
|
||||
reader = csv.reader(f, delimiter=',')
|
||||
@@ -1022,3 +1110,21 @@ class Spreadsheet2VideoNode(io.ComfyNode):
|
||||
return str(random.random())
|
||||
|
||||
|
||||
|
||||
|
||||
def prompt_handler(json_data):
|
||||
try:
|
||||
prompt = json_data["prompt"]
|
||||
map_outputs = MapOutputs()
|
||||
by_group_name = map_outputs.map_outputs(prompt)
|
||||
# remove
|
||||
used_by_other_nodes = map_outputs.nodes_used_by_other_nodes(prompt, by_group_name)
|
||||
GroupInfo.remove_nodes_in_group_infos(prompt, by_group_name, used_by_other_nodes)
|
||||
except Exception:
|
||||
logging.exception("Problem with prompt")
|
||||
return json_data
|
||||
return json_data
|
||||
|
||||
|
||||
PromptServer.instance.add_on_prompt_handler(prompt_handler)
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "spreadsheet2video-comfyui",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "",
|
||||
"license": "ISC",
|
||||
"author": "",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "Spreadsheet2Video-ComfyUI"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
description = "Use a spreadsheet as input for ComfyUI execution"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = [
|
||||
|
||||
+36
-56
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "8d94793d-f68f-467c-b91c-5a1bad8692ca",
|
||||
"revision": 0,
|
||||
"last_node_id": 11,
|
||||
"last_node_id": 13,
|
||||
"last_link_id": 5,
|
||||
"nodes": [
|
||||
{
|
||||
@@ -12,8 +12,8 @@
|
||||
4133.395402282243
|
||||
],
|
||||
"size": [
|
||||
301.040625,
|
||||
106
|
||||
301.03125,
|
||||
142
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
@@ -43,12 +43,12 @@
|
||||
"id": 7,
|
||||
"type": "Spreadsheet2VideoSequence",
|
||||
"pos": [
|
||||
-830.0402038757348,
|
||||
-832.3603875607462,
|
||||
4853.891607434261
|
||||
],
|
||||
"size": [
|
||||
301.040625,
|
||||
106
|
||||
301.015625,
|
||||
142
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
@@ -82,8 +82,8 @@
|
||||
4400
|
||||
],
|
||||
"size": [
|
||||
301.040625,
|
||||
106
|
||||
301.03125,
|
||||
142
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
@@ -117,8 +117,8 @@
|
||||
4101.516095855725
|
||||
],
|
||||
"size": [
|
||||
247.78345320808046,
|
||||
238.2740203378844
|
||||
247.765625,
|
||||
238.25
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
@@ -142,11 +142,7 @@
|
||||
"ver": "0.24.0",
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
false
|
||||
]
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
@@ -156,8 +152,8 @@
|
||||
4430
|
||||
],
|
||||
"size": [
|
||||
226.80773384673182,
|
||||
212.1823816679189
|
||||
226.796875,
|
||||
212.171875
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
@@ -183,22 +179,18 @@
|
||||
"ver": "0.24.0",
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "Spreadsheet2VideoSequence",
|
||||
"pos": [
|
||||
-827.5216964337976,
|
||||
-829.841880118809,
|
||||
5156.965602783869
|
||||
],
|
||||
"size": [
|
||||
301.040625,
|
||||
106
|
||||
301.015625,
|
||||
142
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
@@ -228,11 +220,11 @@
|
||||
"id": 11,
|
||||
"type": "PreviewAny",
|
||||
"pos": [
|
||||
-469.36544276581486,
|
||||
-471.6856264508262,
|
||||
5404.420565428257
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
225,
|
||||
166
|
||||
],
|
||||
"flags": {},
|
||||
@@ -257,22 +249,18 @@
|
||||
"ver": "0.24.0",
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "Spreadsheet2VideoSequence",
|
||||
"pos": [
|
||||
-827.1824057491543,
|
||||
-829.5025894341657,
|
||||
5402.336211698525
|
||||
],
|
||||
"size": [
|
||||
301.040625,
|
||||
106
|
||||
301.015625,
|
||||
142
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
@@ -302,11 +290,11 @@
|
||||
"id": 9,
|
||||
"type": "PreviewAny",
|
||||
"pos": [
|
||||
-469.70473345045866,
|
||||
-472.02491713547,
|
||||
5159.049956513601
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
225,
|
||||
166
|
||||
],
|
||||
"flags": {},
|
||||
@@ -331,21 +319,17 @@
|
||||
"ver": "0.24.0",
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "PreviewAny",
|
||||
"pos": [
|
||||
-472.22324089239635,
|
||||
-474.5434245774077,
|
||||
4855.975961163992
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
225,
|
||||
166
|
||||
],
|
||||
"flags": {},
|
||||
@@ -370,11 +354,7 @@
|
||||
"ver": "0.24.0",
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -422,14 +402,14 @@
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Will Error",
|
||||
"title": "Should Error",
|
||||
"bounding": [
|
||||
-891.0076443313377,
|
||||
-893.327828016349,
|
||||
4744.143303391399,
|
||||
1146.6399876260243,
|
||||
883.1031726327501
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"color": "#A88",
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
@@ -448,13 +428,13 @@
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.5131581182307067,
|
||||
"scale": 0.31863081771035706,
|
||||
"offset": [
|
||||
2097.969476454836,
|
||||
-3598.220857052559
|
||||
2004.6716575603311,
|
||||
-3393.3513028960815
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.45.15",
|
||||
"frontendVersion": "1.48.7",
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
|
||||
Reference in New Issue
Block a user