├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── node.py └── workflow ├── TI2V.jpg ├── TI2V.json ├── TI2V_API.jpg └── TI2V_API.json /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 stepfun-ai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ComfyUI-StepVideo 2 | This repository contains ComfyUI custom nodes for StepVideo. 3 | 4 | ## TI2V 5 | ### Workflow 6 | [TI2V.json](workflow/TI2V.json) 7 | ![TI2V](workflow/TI2V.jpg) 8 | 9 | ### Model Weights 10 | Download the model weights from [this link](https://huggingface.co/stepfun-ai/stepvideo-ti2v). 11 | 12 | ### Install 13 | 1. Install [Step-Video-TI2V](https://github.com/stepfun-ai/Step-Video-TI2V) 14 | 15 | 2. Install ComfyUI custom nodes for StepVideo 16 | ```bash 17 | cd ComfyUI/custom_nodes 18 | git clone https://github.com/stepfun-ai/ComfyUI-StepVideo.git 19 | ``` 20 | 21 | ### Inference 22 | 1. Launch Step-Video-TI2V remote_server 23 | ```bash 24 | cd Step-Video-TI2V 25 | python api/call_remote_server.py --model_dir where_you_download_dir & ## We assume you have more than 4 GPUs available. This command will return the URL for both the caption API and the VAE API. Please use the returned URL as "remote_server_url" parameter in the "TI2V" node. 26 | ``` 27 | 28 | 2. Launch ComfyUI 29 | ```bash 30 | cd ComfyUI 31 | python main.py 32 | ``` 33 | 34 | ## TI2V_API 35 | ### Workflow 36 | [TI2V_API.json](workflow/TI2V_API.json) 37 | ![TI2V_API](workflow/TI2V_API.jpg) 38 | 39 | ### Usage 40 | api_url: https://api.stepfun.com/v1/video/generations 41 | 42 | api_key: get api_key from https://platform.stepfun.com 43 | 44 | ## Todo 45 | - [x] TI2V node 46 | - [x] TI2V_API node 47 | - [ ] T2V node 48 | - [ ] T2V_API node -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS 2 | 3 | __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] -------------------------------------------------------------------------------- /node.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import time 4 | import json 5 | from torchvision.io import read_video 6 | from PIL import Image 7 | import requests 8 | import base64 9 | from tqdm import tqdm 10 | 11 | 12 | def save_hwc_tensor_as_png(tensor, filename): 13 | # 确保张量在CPU且无梯度,并转换为HWC格式的NumPy数组 14 | np_array = tensor.detach().cpu().numpy() 15 | # 缩放值域并转换为uint8 16 | np_array = (np_array * 255).astype('uint8') 17 | # 使用PIL保存图像 18 | Image.fromarray(np_array).save(filename) 19 | 20 | 21 | class TI2V: 22 | CATEGORY = "StepVideo" 23 | 24 | @classmethod 25 | def INPUT_TYPES(s): 26 | return { 27 | "required": { 28 | "image_input": ("IMAGE",), 29 | "remote_server_url": ("STRING", { 30 | "multiline": False, 31 | "default": "127.0.0.1", 32 | "lazy": True 33 | }), 34 | "model_dir": ("STRING", { 35 | "multiline": False, 36 | "default": "", 37 | "lazy": True 38 | }), 39 | "script_dir": ("STRING", { 40 | "multiline": False, 41 | "default": "", 42 | "lazy": True 43 | }), 44 | "infer_steps": ("INT", { 45 | "default": 50, 46 | "min": 0, #Minimum value 47 | "max": 100, #Maximum value 48 | "step": 1, #Slider's step 49 | "display": "number", # Cosmetic only: display as "number" or "slider" 50 | # "lazy": True # Will only be evaluated if check_lazy_status requires it 51 | }), 52 | "cfg_scale": ("FLOAT", { 53 | "default": 9, 54 | "min": 0.0, 55 | "max": 50.0, 56 | "step": 0.1, 57 | # "round": 0.001, #The value representing the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. 58 | "display": "number", 59 | # "lazy": True 60 | }), 61 | "time_shift": ("FLOAT", { 62 | "default": 13.0, 63 | "min": 0.0, 64 | "max": 50.0, 65 | "step": 0.1, 66 | # "round": 0.001, #The value representing the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. 67 | "display": "number", 68 | # "lazy": True 69 | }), 70 | "num_frames": ("INT", { 71 | "default": 102, 72 | "min": 0, #Minimum value 73 | "max": 204, #Maximum value 74 | "step": 1, #Slider's step 75 | "display": "number", # Cosmetic only: display as "number" or "slider" 76 | # "lazy": True # Will only be evaluated if check_lazy_status requires it 77 | }), 78 | "motion_score": ("FLOAT", { 79 | "default": 5, 80 | "min": 0.0, 81 | "max": 50.0, 82 | "step": 0.1, 83 | # "round": 0.001, #The value representing the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. 84 | "display": "number", 85 | # "lazy": True 86 | }), 87 | "text_prompt": ("STRING", { 88 | "multiline": True, 89 | "default": "笑起来", 90 | "lazy": True 91 | }), 92 | } 93 | } 94 | 95 | RETURN_TYPES = ("IMAGE",) 96 | FUNCTION = "ti2v" 97 | 98 | def ti2v(self, image_input, remote_server_url, model_dir, script_dir, infer_steps, cfg_scale, time_shift, num_frames, motion_score, text_prompt): 99 | os.makedirs(f'{script_dir}/results', exist_ok=True) 100 | 101 | task_name = text_prompt[:50] 102 | save_hwc_tensor_as_png(image_input[0], f'{script_dir}/results/{task_name}_img.png') 103 | 104 | parallel = 1 # or parallel = 8 105 | 106 | command = f'cd {script_dir} && torchrun --nproc_per_node {parallel} run_parallel.py --model_dir {model_dir} --vae_url {remote_server_url} --caption_url {remote_server_url} --ulysses_degree {parallel} --first_image_path results/{task_name}_img.png --prompt "{text_prompt}" --infer_steps {infer_steps} --cfg_scale {cfg_scale} --time_shift {time_shift} --num_frames {num_frames} --output_file_name {task_name}_vid --motion_score {motion_score} --name_suffix comfyui' 107 | os.system(command) 108 | 109 | video_path = f'{script_dir}/results/{task_name}_vid-comfyui.mp4' 110 | # 读取视频文件,返回形状为 (T, H, W, C) 的uint8张量 111 | video, _, _ = read_video(video_path, pts_unit="sec") # 默认output_format="THWC" 112 | # 转换为浮点张量并归一化到0~1范围 113 | video_tensor = video.to(torch.float32) / 255.0 114 | 115 | return (video_tensor,) 116 | 117 | 118 | class TI2V_API: 119 | CATEGORY = "StepVideo" 120 | 121 | @classmethod 122 | def INPUT_TYPES(s): 123 | return { 124 | "required": { 125 | "image_input": ("IMAGE",), 126 | "api_url": ("STRING", { 127 | "multiline": False, 128 | "default": "https://api.stepfun.com/v1/video/generations", 129 | "lazy": True 130 | }), 131 | "api_key": ("STRING", { 132 | "multiline": False, 133 | "default": "", 134 | "lazy": True 135 | }), 136 | "video_size": (['960x540', '544x992', '768x768'], {"default": '960x540'}), 137 | "text_prompt": ("STRING", { 138 | "multiline": True, 139 | "default": "笑起来", 140 | "lazy": True 141 | }), 142 | } 143 | } 144 | 145 | RETURN_TYPES = ("IMAGE",) 146 | FUNCTION = "ti2v" 147 | 148 | 149 | def download_video(self, url, save_path=None, chunk_size=1024): 150 | """ 151 | 下载视频文件到本地 152 | 153 | 参数: 154 | url (str): 视频URL 155 | save_path (str): 保存路径(可选) 156 | chunk_size (int): 下载块大小(默认1024字节) 157 | 158 | 返回: 159 | str: 最终保存路径 160 | """ 161 | try: 162 | # 设置默认保存路径 163 | if not save_path: 164 | filename = url.split("/")[-1].split("?")[0] # 从URL提取文件名 165 | save_path = os.path.join(os.getcwd(), filename) 166 | 167 | # 创建请求头(模拟浏览器) 168 | headers = { 169 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" 170 | } 171 | 172 | # 发起带流式传输的GET请求 173 | response = requests.get(url, headers=headers, stream=True) 174 | response.raise_for_status() # 检查HTTP错误 175 | 176 | # 获取文件总大小(字节) 177 | total_size = int(response.headers.get('content-length', 0)) 178 | 179 | # 创建进度条 180 | progress = tqdm( 181 | total=total_size, 182 | unit='B', 183 | unit_scale=True, 184 | desc=f"Downloading {os.path.basename(save_path)}" 185 | ) 186 | 187 | # 写入文件 188 | with open(save_path, 'wb') as f: 189 | for chunk in response.iter_content(chunk_size=chunk_size): 190 | if chunk: # 过滤保持连接的空白块 191 | f.write(chunk) 192 | progress.update(len(chunk)) 193 | progress.close() 194 | 195 | # 验证文件大小 196 | if total_size != 0 and progress.n != total_size: 197 | raise RuntimeError("下载不完整,请重试") 198 | 199 | return save_path 200 | 201 | except requests.exceptions.RequestException as e: 202 | print(f"下载失败: {str(e)}") 203 | if os.path.exists(save_path): 204 | os.remove(save_path) # 删除不完整文件 205 | return None 206 | except Exception as e: 207 | print(f"发生未知错误: {str(e)}") 208 | return None 209 | 210 | 211 | def ti2v(self, image_input, api_url, api_key, video_size, text_prompt): 212 | task_dir = 'output' 213 | os.makedirs(f'{task_dir}', exist_ok=True) 214 | 215 | # 准备图片 216 | task_name = text_prompt[:50] 217 | img_path = f'{task_dir}/{task_name}_img.png' 218 | save_hwc_tensor_as_png(image_input[0], img_path) 219 | 220 | # 请求内容 221 | with open(img_path, "rb") as img_file: 222 | encoded = base64.b64encode(img_file.read()).decode('utf-8') 223 | image_b64 = f"data:image/png;base64,{encoded}" 224 | headers = { 225 | "Authorization": f"Bearer {api_key}", 226 | "Content-Type": "application/json", 227 | } 228 | payload = { 229 | "model": "step-video", 230 | "image_b64": image_b64, 231 | "prompt": text_prompt, 232 | 'size': video_size, 233 | } 234 | 235 | # 上传请求 236 | response_post = requests.post( 237 | api_url, 238 | headers=headers, 239 | json=payload, 240 | ) 241 | print(f'{response_post.content=}') 242 | 243 | 244 | # 获取生成任务 task_id 245 | response = json.loads(response_post.content) 246 | if response['status'] == 'fail': 247 | raise Exception('generation failed') 248 | else: 249 | task_id = response['task_id'] 250 | 251 | # 等待视频生成 252 | while True: 253 | response_get = requests.get( 254 | f"{api_url}/{task_id}", 255 | headers=headers, 256 | ) 257 | print(f'{response_get.content=}') 258 | response = json.loads(response_get.content) 259 | 260 | if response['status'] == 'fail': 261 | raise Exception('generation failed') 262 | elif response['status'] == 'success': 263 | url = response['video']['url'] 264 | break 265 | else: 266 | time.sleep(10) 267 | 268 | # 下载视频 269 | video_path = f'{task_dir}/{task_name}_vid.mp4' 270 | self.download_video(url, save_path=video_path) 271 | 272 | # 读取视频文件,返回形状为 (T, H, W, C) 的uint8张量 273 | video, _, _ = read_video(video_path, pts_unit="sec") # 默认output_format="THWC" 274 | # 转换为浮点张量并归一化到0~1范围 275 | video_tensor = video.to(torch.float32) / 255.0 276 | 277 | return (video_tensor,) 278 | 279 | 280 | NODE_CLASS_MAPPINGS = { 281 | "TI2V" : TI2V, 282 | "TI2V_API" : TI2V_API, 283 | } 284 | 285 | # Optionally, you can rename the node in the `NODE_DISPLAY_NAME_MAPPINGS` dictionary. 286 | NODE_DISPLAY_NAME_MAPPINGS = { 287 | "TI2V": "TI2V", 288 | "TI2V_API": "TI2V_API", 289 | } -------------------------------------------------------------------------------- /workflow/TI2V.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepfun-ai/ComfyUI-StepVideo/da49ebf16f22274e9d51847826f8e3a11f514709/workflow/TI2V.jpg -------------------------------------------------------------------------------- /workflow/TI2V.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 19, 3 | "last_link_id": 30, 4 | "nodes": [ 5 | { 6 | "id": 12, 7 | "type": "VHS_VideoCombine", 8 | "pos": [ 9 | 1108.92138671875, 10 | -27.019807815551758 11 | ], 12 | "size": [ 13 | 214.7587890625, 14 | 457.55181884765625 15 | ], 16 | "flags": {}, 17 | "order": 2, 18 | "mode": 0, 19 | "inputs": [ 20 | { 21 | "name": "images", 22 | "type": "IMAGE", 23 | "link": 30 24 | }, 25 | { 26 | "name": "audio", 27 | "type": "AUDIO", 28 | "shape": 7, 29 | "link": null 30 | }, 31 | { 32 | "name": "meta_batch", 33 | "type": "VHS_BatchManager", 34 | "shape": 7, 35 | "link": null 36 | }, 37 | { 38 | "name": "vae", 39 | "type": "VAE", 40 | "shape": 7, 41 | "link": null 42 | } 43 | ], 44 | "outputs": [ 45 | { 46 | "name": "Filenames", 47 | "type": "VHS_FILENAMES", 48 | "links": null 49 | } 50 | ], 51 | "properties": { 52 | "Node name for S&R": "VHS_VideoCombine" 53 | }, 54 | "widgets_values": { 55 | "frame_rate": 20, 56 | "loop_count": 0, 57 | "filename_prefix": "AnimateDiff", 58 | "format": "video/h264-mp4", 59 | "pix_fmt": "yuv420p", 60 | "crf": 19, 61 | "save_metadata": true, 62 | "trim_to_audio": false, 63 | "pingpong": false, 64 | "save_output": false, 65 | "videopreview": { 66 | "hidden": false, 67 | "paused": true, 68 | "params": { 69 | "filename": "AnimateDiff_00004.mp4", 70 | "subfolder": "", 71 | "type": "temp", 72 | "format": "video/h264-mp4", 73 | "frame_rate": 20, 74 | "workflow": "AnimateDiff_00004.png", 75 | "fullpath": "/data/script/ComfyUI/temp/AnimateDiff_00004.mp4" 76 | } 77 | } 78 | } 79 | }, 80 | { 81 | "id": 3, 82 | "type": "LoadImage", 83 | "pos": [ 84 | 312.82855224609375, 85 | -25.91734504699707 86 | ], 87 | "size": [ 88 | 315, 89 | 314 90 | ], 91 | "flags": {}, 92 | "order": 0, 93 | "mode": 0, 94 | "inputs": [], 95 | "outputs": [ 96 | { 97 | "name": "IMAGE", 98 | "type": "IMAGE", 99 | "links": [ 100 | 29 101 | ], 102 | "slot_index": 0 103 | }, 104 | { 105 | "name": "MASK", 106 | "type": "MASK", 107 | "links": null 108 | } 109 | ], 110 | "properties": { 111 | "Node name for S&R": "LoadImage" 112 | }, 113 | "widgets_values": [ 114 | "pexels-olly-3777931.png", 115 | "image" 116 | ] 117 | }, 118 | { 119 | "id": 19, 120 | "type": "TI2V", 121 | "pos": [ 122 | 667.665283203125, 123 | -26.430679321289062 124 | ], 125 | "size": [ 126 | 400, 127 | 280 128 | ], 129 | "flags": {}, 130 | "order": 1, 131 | "mode": 0, 132 | "inputs": [ 133 | { 134 | "name": "image_input", 135 | "type": "IMAGE", 136 | "link": 29 137 | } 138 | ], 139 | "outputs": [ 140 | { 141 | "name": "IMAGE", 142 | "type": "IMAGE", 143 | "links": [ 144 | 30 145 | ], 146 | "slot_index": 0 147 | } 148 | ], 149 | "properties": { 150 | "Node name for S&R": "TI2V" 151 | }, 152 | "widgets_values": [ 153 | "100.96.67.87", 154 | "/data/script/check/model/stepvideo-ti2v", 155 | "/data/script/check/Step-Video-TI2V", 156 | 15, 157 | 9, 158 | 13, 159 | 51, 160 | 5, 161 | "闭上眼睛。" 162 | ] 163 | } 164 | ], 165 | "links": [ 166 | [ 167 | 29, 168 | 3, 169 | 0, 170 | 19, 171 | 0, 172 | "IMAGE" 173 | ], 174 | [ 175 | 30, 176 | 19, 177 | 0, 178 | 12, 179 | 0, 180 | "IMAGE" 181 | ] 182 | ], 183 | "groups": [], 184 | "config": {}, 185 | "extra": { 186 | "ds": { 187 | "scale": 0.9090909090909091, 188 | "offset": [ 189 | -257.5852157075672, 190 | 119.49067721285375 191 | ] 192 | }, 193 | "VHS_latentpreview": false, 194 | "VHS_latentpreviewrate": 0, 195 | "VHS_MetadataImage": true, 196 | "VHS_KeepIntermediate": true 197 | }, 198 | "version": 0.4 199 | } -------------------------------------------------------------------------------- /workflow/TI2V_API.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepfun-ai/ComfyUI-StepVideo/da49ebf16f22274e9d51847826f8e3a11f514709/workflow/TI2V_API.jpg -------------------------------------------------------------------------------- /workflow/TI2V_API.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 26, 3 | "last_link_id": 41, 4 | "nodes": [ 5 | { 6 | "id": 3, 7 | "type": "LoadImage", 8 | "pos": [ 9 | 320.0885009765625, 10 | 37.970645904541016 11 | ], 12 | "size": [ 13 | 315, 14 | 314 15 | ], 16 | "flags": {}, 17 | "order": 0, 18 | "mode": 0, 19 | "inputs": [], 20 | "outputs": [ 21 | { 22 | "name": "IMAGE", 23 | "type": "IMAGE", 24 | "links": [ 25 | 41 26 | ], 27 | "slot_index": 0 28 | }, 29 | { 30 | "name": "MASK", 31 | "type": "MASK", 32 | "links": null 33 | } 34 | ], 35 | "properties": { 36 | "Node name for S&R": "LoadImage" 37 | }, 38 | "widgets_values": [ 39 | "pexels-olly-3777931.png", 40 | "image" 41 | ] 42 | }, 43 | { 44 | "id": 26, 45 | "type": "VHS_VideoCombine", 46 | "pos": [ 47 | 1150.8505859375, 48 | 37.46379089355469 49 | ], 50 | "size": [ 51 | 214.7587890625, 52 | 457.55181884765625 53 | ], 54 | "flags": { 55 | "collapsed": false 56 | }, 57 | "order": 2, 58 | "mode": 0, 59 | "inputs": [ 60 | { 61 | "name": "images", 62 | "type": "IMAGE", 63 | "link": 40 64 | }, 65 | { 66 | "name": "audio", 67 | "type": "AUDIO", 68 | "shape": 7, 69 | "link": null 70 | }, 71 | { 72 | "name": "meta_batch", 73 | "type": "VHS_BatchManager", 74 | "shape": 7, 75 | "link": null 76 | }, 77 | { 78 | "name": "vae", 79 | "type": "VAE", 80 | "shape": 7, 81 | "link": null 82 | } 83 | ], 84 | "outputs": [ 85 | { 86 | "name": "Filenames", 87 | "type": "VHS_FILENAMES", 88 | "links": null 89 | } 90 | ], 91 | "properties": { 92 | "Node name for S&R": "VHS_VideoCombine" 93 | }, 94 | "widgets_values": { 95 | "frame_rate": 20, 96 | "loop_count": 0, 97 | "filename_prefix": "AnimateDiff", 98 | "format": "video/h264-mp4", 99 | "pix_fmt": "yuv420p", 100 | "crf": 19, 101 | "save_metadata": true, 102 | "trim_to_audio": false, 103 | "pingpong": false, 104 | "save_output": false, 105 | "videopreview": { 106 | "hidden": false, 107 | "paused": true, 108 | "params": { 109 | "filename": "AnimateDiff_00002.mp4", 110 | "subfolder": "", 111 | "type": "temp", 112 | "format": "video/h264-mp4", 113 | "frame_rate": 20, 114 | "workflow": "AnimateDiff_00002.png", 115 | "fullpath": "/data/script/ComfyUI/temp/AnimateDiff_00002.mp4" 116 | } 117 | } 118 | } 119 | }, 120 | { 121 | "id": 24, 122 | "type": "TI2V_API", 123 | "pos": [ 124 | 683.3611450195312, 125 | 39.218997955322266 126 | ], 127 | "size": [ 128 | 400, 129 | 200 130 | ], 131 | "flags": {}, 132 | "order": 1, 133 | "mode": 0, 134 | "inputs": [ 135 | { 136 | "name": "image_input", 137 | "type": "IMAGE", 138 | "link": 41 139 | } 140 | ], 141 | "outputs": [ 142 | { 143 | "name": "IMAGE", 144 | "type": "IMAGE", 145 | "links": [ 146 | 40 147 | ], 148 | "slot_index": 0 149 | } 150 | ], 151 | "properties": { 152 | "Node name for S&R": "TI2V_API" 153 | }, 154 | "widgets_values": [ 155 | "https://api.stepfun.com/v1/video/generations", 156 | "6iHvPHPJwRpX10Odurf1H64FnABN1iHpGBaHf5BqhH5RBNKAwZw1sWnuWtMEZ43k5", 157 | "960x540", 158 | "闭上眼睛" 159 | ] 160 | } 161 | ], 162 | "links": [ 163 | [ 164 | 40, 165 | 24, 166 | 0, 167 | 26, 168 | 0, 169 | "IMAGE" 170 | ], 171 | [ 172 | 41, 173 | 3, 174 | 0, 175 | 24, 176 | 0, 177 | "IMAGE" 178 | ] 179 | ], 180 | "groups": [], 181 | "config": {}, 182 | "extra": { 183 | "ds": { 184 | "scale": 0.90909090909091, 185 | "offset": [ 186 | -289.2615733441576, 187 | 58.084385512189 188 | ] 189 | }, 190 | "VHS_latentpreview": false, 191 | "VHS_latentpreviewrate": 0, 192 | "VHS_MetadataImage": true, 193 | "VHS_KeepIntermediate": true 194 | }, 195 | "version": 0.4 196 | } --------------------------------------------------------------------------------