Compare commits

...

2 Commits

Author SHA1 Message Date
6aa51c14a2 Merge branch 'master' of https://git.yasue.org/ken/RandomVideoMaker 2025-08-23 22:17:36 +02:00
37baaf72ab save changes 2025-08-23 22:17:27 +02:00
8 changed files with 1695 additions and 5 deletions

15
package-lock.json generated
View File

@ -14,7 +14,8 @@
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"mysql2": "^3.14.3", "mysql2": "^3.14.3",
"open": "^10.2.0", "open": "^10.2.0",
"puppeteer": "^24.16.2" "puppeteer": "^24.16.2",
"uuid": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
@ -1576,6 +1577,18 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"devOptional": true "devOptional": true
}, },
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": { "node_modules/v8-compile-cache-lib": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",

View File

@ -6,9 +6,9 @@
"scripts": { "scripts": {
"start": "tsc && node dist/index.js", "start": "tsc && node dist/index.js",
"build": "tsc", "build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"db:schema": "ts-node src/schema.ts", "db:schema": "ts-node src/schema.ts",
"db:test": "ts-node src/testmysql.ts" "db:test": "ts-node src/testmysql.ts"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -24,6 +24,7 @@
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"mysql2": "^3.14.3", "mysql2": "^3.14.3",
"open": "^10.2.0", "open": "^10.2.0",
"puppeteer": "^24.16.2" "puppeteer": "^24.16.2",
"uuid": "^11.1.0"
} }
} }

View File

@ -0,0 +1,581 @@
{
"70": {
"inputs": {
"text": "色调艳丽过曝静态细节模糊不清字幕风格作品画作画面静止整体发灰最差质量低质量JPEG压缩残留丑陋的残缺的多余的手指画得不好的手部画得不好的脸部畸形的毁容的形态畸形的肢体手指融合静止不动的画面杂乱的背景三条腿背景人很多倒着走",
"clip": [
"82",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Negative Prompt)"
}
},
"73": {
"inputs": {
"vae_name": "wan_2.1_vae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "Load VAE"
}
},
"76": {
"inputs": {
"enable_fp16_accumulation": true,
"model": [
"77",
0
]
},
"class_type": "ModelPatchTorchSettings",
"_meta": {
"title": "Model Patch Torch Settings"
}
},
"77": {
"inputs": {
"sage_attention": "sageattn_qk_int8_pv_fp8_cuda++",
"model": [
"83",
0
]
},
"class_type": "PathchSageAttentionKJ",
"_meta": {
"title": "Patch Sage Attention KJ"
}
},
"78": {
"inputs": {
"sage_attention": "sageattn_qk_int8_pv_fp8_cuda++",
"model": [
"84",
0
]
},
"class_type": "PathchSageAttentionKJ",
"_meta": {
"title": "Patch Sage Attention KJ"
}
},
"79": {
"inputs": {
"enable_fp16_accumulation": true,
"model": [
"78",
0
]
},
"class_type": "ModelPatchTorchSettings",
"_meta": {
"title": "Model Patch Torch Settings"
}
},
"80": {
"inputs": {
"shift": 8.000000000000002,
"model": [
"89",
0
]
},
"class_type": "ModelSamplingSD3",
"_meta": {
"title": "ModelSamplingSD3"
}
},
"81": {
"inputs": {
"shift": 8.000000000000002,
"model": [
"90",
0
]
},
"class_type": "ModelSamplingSD3",
"_meta": {
"title": "ModelSamplingSD3"
}
},
"82": {
"inputs": {
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"type": "wan",
"device": "cpu"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "Load CLIP"
}
},
"83": {
"inputs": {
"unet_name": "wan2.2_i2v_high_noise_14B_Q4_K_S.gguf"
},
"class_type": "UnetLoaderGGUF",
"_meta": {
"title": "Unet Loader (GGUF)"
}
},
"84": {
"inputs": {
"unet_name": "wan2.2_i2v_low_noise_14B_Q4_K_S.gguf"
},
"class_type": "UnetLoaderGGUF",
"_meta": {
"title": "Unet Loader (GGUF)"
}
},
"85": {
"inputs": {
"frame_rate": 32,
"loop_count": 0,
"filename_prefix": "STYLEDVIDEOMAKER",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 19,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"88",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"86": {
"inputs": {
"upscale_model": "4x-UltraSharp.pth",
"mode": "rescale",
"rescale_factor": 2.0000000000000004,
"resize_width": 832,
"resampling_method": "lanczos",
"supersample": "true",
"rounding_modulus": 8
},
"class_type": "CR Upscale Image",
"_meta": {
"title": "🔍 CR Upscale Image"
}
},
"87": {
"inputs": {
"samples": [
"92",
0
],
"vae": [
"73",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"88": {
"inputs": {
"ckpt_name": "rife49.pth",
"clear_cache_after_n_frames": 10,
"multiplier": 2,
"fast_mode": true,
"ensemble": true,
"scale_factor": 1,
"frames": [
"98",
0
]
},
"class_type": "RIFE VFI",
"_meta": {
"title": "RIFE VFI (recommend rife47 and rife49)"
}
},
"89": {
"inputs": {
"lora_name": "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
"strength_model": 3.0000000000000004,
"model": [
"76",
0
]
},
"class_type": "LoraLoaderModelOnly",
"_meta": {
"title": "LoraLoaderModelOnly"
}
},
"90": {
"inputs": {
"lora_name": "Wan21_T2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
"strength_model": 1.5000000000000002,
"model": [
"79",
0
]
},
"class_type": "LoraLoaderModelOnly",
"_meta": {
"title": "LoraLoaderModelOnly"
}
},
"91": {
"inputs": {
"add_noise": "enable",
"noise_seed": 452107028428,
"steps": 6,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 0,
"end_at_step": 3,
"return_with_leftover_noise": "enable",
"model": [
"80",
0
],
"positive": [
"96",
0
],
"negative": [
"96",
1
],
"latent_image": [
"96",
2
]
},
"class_type": "KSamplerAdvanced",
"_meta": {
"title": "KSampler (Advanced)"
}
},
"92": {
"inputs": {
"add_noise": "disable",
"noise_seed": 0,
"steps": 6,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 3,
"end_at_step": 10000,
"return_with_leftover_noise": "disable",
"model": [
"81",
0
],
"positive": [
"96",
0
],
"negative": [
"96",
1
],
"latent_image": [
"91",
0
]
},
"class_type": "KSamplerAdvanced",
"_meta": {
"title": "KSampler (Advanced)"
}
},
"93": {
"inputs": {
"model": "4xNomos2_otf_esrgan",
"precision": "fp16"
},
"class_type": "LoadUpscalerTensorrtModel",
"_meta": {
"title": "Load Upscale Tensorrt Model"
}
},
"95": {
"inputs": {
"text": "A luminous ballerina in mid-performance, illustrated in glowing white strokes against a deep black background. She balances gracefully en pointe on one foot, arms extended in a fluid pose, her tutu radiating light and motion like ethereal fabric made of starlight. Her hair flows upward, sketched in swirling white lines, blending with scattered glowing stars around her. The reflection of her figure shimmers on a dark water surface below, surrounded by circular ripples of light. The style is dreamy, abstract, and expressive, combining sketch-like brush strokes with glowing energy lines. High contrast, elegant, surreal ballet illustration.",
"clip": [
"82",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Positive Prompt)"
}
},
"96": {
"inputs": {
"width": [
"97",
1
],
"height": [
"97",
2
],
"length": 121,
"batch_size": 1,
"positive": [
"95",
0
],
"negative": [
"70",
0
],
"vae": [
"73",
0
],
"start_image": [
"97",
0
]
},
"class_type": "WanImageToVideo",
"_meta": {
"title": "WanImageToVideo"
}
},
"97": {
"inputs": {
"width": 320,
"height": 640,
"upscale_method": "lanczos",
"keep_proportion": "crop",
"pad_color": "0, 0, 0",
"crop_position": "center",
"divisible_by": 16,
"device": "cpu",
"image": [
"103",
0
]
},
"class_type": "ImageResizeKJv2",
"_meta": {
"title": "Resize Image v2"
}
},
"98": {
"inputs": {
"resize_to": "4k",
"images": [
"87",
0
],
"upscaler_trt_model": [
"93",
0
]
},
"class_type": "UpscalerTensorrt",
"_meta": {
"title": "Upscaler Tensorrt ⚡"
}
},
"99": {
"inputs": {
"filename_prefix": "STYLEDVIDEOMAKER",
"images": [
"103",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
},
"100": {
"inputs": {
"clip_name1": "clip_l.safetensors",
"clip_name2": "t5xxl_fp16.safetensors",
"type": "flux",
"device": "default"
},
"class_type": "DualCLIPLoader",
"_meta": {
"title": "DualCLIPLoader"
}
},
"101": {
"inputs": {
"vae_name": "ae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "Load VAE"
}
},
"102": {
"inputs": {
"conditioning": [
"105",
0
]
},
"class_type": "ConditioningZeroOut",
"_meta": {
"title": "ConditioningZeroOut"
}
},
"103": {
"inputs": {
"samples": [
"106",
0
],
"vae": [
"101",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"104": {
"inputs": {
"width": 320,
"height": 640,
"batch_size": 1
},
"class_type": "EmptySD3LatentImage",
"_meta": {
"title": "EmptySD3LatentImage"
}
},
"105": {
"inputs": {
"text": "A luminous ballerina in mid-performance, illustrated in glowing white strokes against a deep black background. She balances gracefully en pointe on one foot, arms extended in a fluid pose, her tutu radiating light and motion like ethereal fabric made of starlight. Her hair flows upward, sketched in swirling white lines, blending with scattered glowing stars around her. The reflection of her figure shimmers on a dark water surface below, surrounded by circular ripples of light. The style is dreamy, abstract, and expressive, combining sketch-like brush strokes with glowing energy lines. High contrast, elegant, surreal ballet illustration.",
"clip": [
"100",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"106": {
"inputs": {
"seed": 84283616550942,
"steps": 20,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"denoise": 1,
"model": [
"107",
0
],
"positive": [
"111",
0
],
"negative": [
"102",
0
],
"latent_image": [
"104",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"107": {
"inputs": {
"unet_name": "flux1-krea-dev_fp8_scaled.safetensors",
"weight_dtype": "default"
},
"class_type": "UNETLoader",
"_meta": {
"title": "Load Diffusion Model"
}
},
"110": {
"inputs": {
"style_model_name": "flux1-redux-dev.safetensors"
},
"class_type": "StyleModelLoader",
"_meta": {
"title": "Load Style Model"
}
},
"111": {
"inputs": {
"strength": 0.15,
"conditioning": [
"105",
0
],
"style_model": [
"110",
0
],
"clip_vision_output": [
"112",
0
]
},
"class_type": "ApplyStyleModelAdjust",
"_meta": {
"title": "Apply Style Model (Adjusted)"
}
},
"112": {
"inputs": {
"crop": "center",
"clip_vision": [
"113",
0
],
"image": [
"114",
0
]
},
"class_type": "CLIPVisionEncode",
"_meta": {
"title": "CLIP Vision Encode"
}
},
"113": {
"inputs": {
"clip_name": "sigclip_vision_patch14_384.safetensors"
},
"class_type": "CLIPVisionLoader",
"_meta": {
"title": "Load CLIP Vision"
}
},
"114": {
"inputs": {
"image": "440fb3cb2bcc993bdc7da34986a7135d.jpg"
},
"class_type": "LoadImage",
"_meta": {
"title": "Load Image"
}
}
}

View File

@ -0,0 +1,545 @@
{
"70": {
"inputs": {
"text": "色调艳丽过曝静态细节模糊不清字幕风格作品画作画面静止整体发灰最差质量低质量JPEG压缩残留丑陋的残缺的多余的手指画得不好的手部画得不好的脸部畸形的毁容的形态畸形的肢体手指融合静止不动的画面杂乱的背景三条腿背景人很多倒着走",
"clip": [
"82",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Negative Prompt)"
}
},
"73": {
"inputs": {
"vae_name": "wan_2.1_vae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "Load VAE"
}
},
"76": {
"inputs": {
"enable_fp16_accumulation": true,
"model": [
"77",
0
]
},
"class_type": "ModelPatchTorchSettings",
"_meta": {
"title": "Model Patch Torch Settings"
}
},
"77": {
"inputs": {
"sage_attention": "sageattn_qk_int8_pv_fp8_cuda++",
"model": [
"83",
0
]
},
"class_type": "PathchSageAttentionKJ",
"_meta": {
"title": "Patch Sage Attention KJ"
}
},
"78": {
"inputs": {
"sage_attention": "sageattn_qk_int8_pv_fp8_cuda++",
"model": [
"84",
0
]
},
"class_type": "PathchSageAttentionKJ",
"_meta": {
"title": "Patch Sage Attention KJ"
}
},
"79": {
"inputs": {
"enable_fp16_accumulation": true,
"model": [
"78",
0
]
},
"class_type": "ModelPatchTorchSettings",
"_meta": {
"title": "Model Patch Torch Settings"
}
},
"80": {
"inputs": {
"shift": 8.000000000000002,
"model": [
"89",
0
]
},
"class_type": "ModelSamplingSD3",
"_meta": {
"title": "ModelSamplingSD3"
}
},
"81": {
"inputs": {
"shift": 8.000000000000002,
"model": [
"90",
0
]
},
"class_type": "ModelSamplingSD3",
"_meta": {
"title": "ModelSamplingSD3"
}
},
"82": {
"inputs": {
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"type": "wan",
"device": "cpu"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "Load CLIP"
}
},
"83": {
"inputs": {
"unet_name": "wan2.2_i2v_high_noise_14B_Q4_K_S.gguf"
},
"class_type": "UnetLoaderGGUF",
"_meta": {
"title": "Unet Loader (GGUF)"
}
},
"84": {
"inputs": {
"unet_name": "wan2.2_i2v_low_noise_14B_Q4_K_S.gguf"
},
"class_type": "UnetLoaderGGUF",
"_meta": {
"title": "Unet Loader (GGUF)"
}
},
"85": {
"inputs": {
"frame_rate": 32,
"loop_count": 0,
"filename_prefix": "STYLEDVIDEOMAKER",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 19,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"87",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"86": {
"inputs": {
"upscale_model": "4x-UltraSharp.pth",
"mode": "rescale",
"rescale_factor": 2.0000000000000004,
"resize_width": 832,
"resampling_method": "lanczos",
"supersample": "true",
"rounding_modulus": 8
},
"class_type": "CR Upscale Image",
"_meta": {
"title": "🔍 CR Upscale Image"
}
},
"87": {
"inputs": {
"samples": [
"92",
0
],
"vae": [
"73",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"89": {
"inputs": {
"lora_name": "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
"strength_model": 3.0000000000000004,
"model": [
"76",
0
]
},
"class_type": "LoraLoaderModelOnly",
"_meta": {
"title": "LoraLoaderModelOnly"
}
},
"90": {
"inputs": {
"lora_name": "Wan21_T2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
"strength_model": 1.5000000000000002,
"model": [
"79",
0
]
},
"class_type": "LoraLoaderModelOnly",
"_meta": {
"title": "LoraLoaderModelOnly"
}
},
"91": {
"inputs": {
"add_noise": "enable",
"noise_seed": 452107028428,
"steps": 6,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 0,
"end_at_step": 3,
"return_with_leftover_noise": "enable",
"model": [
"80",
0
],
"positive": [
"96",
0
],
"negative": [
"96",
1
],
"latent_image": [
"96",
2
]
},
"class_type": "KSamplerAdvanced",
"_meta": {
"title": "KSampler (Advanced)"
}
},
"92": {
"inputs": {
"add_noise": "disable",
"noise_seed": 0,
"steps": 6,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 3,
"end_at_step": 10000,
"return_with_leftover_noise": "disable",
"model": [
"81",
0
],
"positive": [
"96",
0
],
"negative": [
"96",
1
],
"latent_image": [
"91",
0
]
},
"class_type": "KSamplerAdvanced",
"_meta": {
"title": "KSampler (Advanced)"
}
},
"94": {
"inputs": {
"image": "ComfyUI_00036_.png"
},
"class_type": "LoadImage",
"_meta": {
"title": "Load Image"
}
},
"95": {
"inputs": {
"text": "A luminous ballerina in mid-performance, illustrated in glowing white strokes against a deep black background. She balances gracefully en pointe on one foot, arms extended in a fluid pose, her tutu radiating light and motion like ethereal fabric made of starlight. Her hair flows upward, sketched in swirling white lines, blending with scattered glowing stars around her. The reflection of her figure shimmers on a dark water surface below, surrounded by circular ripples of light. The style is dreamy, abstract, and expressive, combining sketch-like brush strokes with glowing energy lines. High contrast, elegant, surreal ballet illustration.",
"clip": [
"82",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Positive Prompt)"
}
},
"96": {
"inputs": {
"width": [
"97",
1
],
"height": [
"97",
2
],
"length": 89,
"batch_size": 1,
"positive": [
"95",
0
],
"negative": [
"70",
0
],
"vae": [
"73",
0
],
"start_image": [
"97",
0
]
},
"class_type": "WanImageToVideo",
"_meta": {
"title": "WanImageToVideo"
}
},
"97": {
"inputs": {
"width": 320,
"height": 640,
"upscale_method": "lanczos",
"keep_proportion": "crop",
"pad_color": "0, 0, 0",
"crop_position": "center",
"divisible_by": 16,
"device": "cpu",
"image": [
"103",
0
]
},
"class_type": "ImageResizeKJv2",
"_meta": {
"title": "Resize Image v2"
}
},
"99": {
"inputs": {
"filename_prefix": "STYLEDVIDEOMAKER",
"images": [
"103",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
},
"100": {
"inputs": {
"clip_name1": "clip_l.safetensors",
"clip_name2": "t5xxl_fp16.safetensors",
"type": "flux",
"device": "default"
},
"class_type": "DualCLIPLoader",
"_meta": {
"title": "DualCLIPLoader"
}
},
"101": {
"inputs": {
"vae_name": "ae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "Load VAE"
}
},
"102": {
"inputs": {
"conditioning": [
"105",
0
]
},
"class_type": "ConditioningZeroOut",
"_meta": {
"title": "ConditioningZeroOut"
}
},
"103": {
"inputs": {
"samples": [
"106",
0
],
"vae": [
"101",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"104": {
"inputs": {
"width": 320,
"height": 640,
"batch_size": 1
},
"class_type": "EmptySD3LatentImage",
"_meta": {
"title": "EmptySD3LatentImage"
}
},
"105": {
"inputs": {
"text": "A luminous ballerina in mid-performance, illustrated in glowing white strokes against a deep black background. She balances gracefully en pointe on one foot, arms extended in a fluid pose, her tutu radiating light and motion like ethereal fabric made of starlight. Her hair flows upward, sketched in swirling white lines, blending with scattered glowing stars around her. The reflection of her figure shimmers on a dark water surface below, surrounded by circular ripples of light. The style is dreamy, abstract, and expressive, combining sketch-like brush strokes with glowing energy lines. High contrast, elegant, surreal ballet illustration.",
"clip": [
"100",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"106": {
"inputs": {
"seed": 84283616550942,
"steps": 20,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"denoise": 1,
"model": [
"107",
0
],
"positive": [
"111",
0
],
"negative": [
"102",
0
],
"latent_image": [
"104",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"107": {
"inputs": {
"unet_name": "flux1-krea-dev_fp8_scaled.safetensors",
"weight_dtype": "default"
},
"class_type": "UNETLoader",
"_meta": {
"title": "Load Diffusion Model"
}
},
"110": {
"inputs": {
"style_model_name": "flux1-redux-dev.safetensors"
},
"class_type": "StyleModelLoader",
"_meta": {
"title": "Load Style Model"
}
},
"111": {
"inputs": {
"strength": 0.15,
"conditioning": [
"105",
0
],
"style_model": [
"110",
0
],
"clip_vision_output": [
"112",
0
]
},
"class_type": "ApplyStyleModelAdjust",
"_meta": {
"title": "Apply Style Model (Adjusted)"
}
},
"112": {
"inputs": {
"crop": "center",
"clip_vision": [
"113",
0
],
"image": [
"114",
0
]
},
"class_type": "CLIPVisionEncode",
"_meta": {
"title": "CLIP Vision Encode"
}
},
"113": {
"inputs": {
"clip_name": "sigclip_vision_patch14_384.safetensors"
},
"class_type": "CLIPVisionLoader",
"_meta": {
"title": "Load CLIP Vision"
}
},
"114": {
"inputs": {
"image": "440fb3cb2bcc993bdc7da34986a7135d.jpg"
},
"class_type": "LoadImage",
"_meta": {
"title": "Load Image"
}
}
}

View File

@ -88,3 +88,78 @@ export async function downloadPinterestImages(keyword: string, numberOfPages: nu
logger.debug('Done.'); logger.debug('Done.');
return downloadedImagePaths; return downloadedImagePaths;
} }
export async function downloadImagesFromPinterestPin(pinUrl: string, scrollCount: number = 5): Promise<string[]> {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36');
await page.setViewport({ width: 1920, height: 1080 });
const downloadedImagePaths: string[] = [];
const downloadedUrls = new Set<string>();
try {
await page.goto(pinUrl, { waitUntil: 'networkidle2' });
logger.debug(`Navigated to pin page: ${pinUrl}`);
const pageImageUrls = new Set<string>();
for (let i = 0; i < scrollCount; i++) {
logger.debug(`Scrolling page ${i + 1}/${scrollCount} for ${pinUrl}...`);
const imageUrlsOnPage = await page.evaluate(() => {
const images = Array.from(document.querySelectorAll('img[src*="i.pinimg.com"]'));
const urls = images.map(img => {
const image = img as HTMLImageElement;
if (image.srcset) {
const sources = image.srcset.split(',').map(s => s.trim().split(' '));
const fourXSource = sources.find(s => s[1] === '4x');
if (fourXSource) {
return fourXSource[0];
}
}
return null; // Return null if no 4x source is found
});
return urls.filter((url): url is string => url !== null); // Filter out nulls and assert type
});
for (const url of imageUrlsOnPage) {
pageImageUrls.add(url);
}
const previousHeight = await page.evaluate('document.body.scrollHeight');
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
try {
await page.waitForFunction(`document.body.scrollHeight > ${previousHeight}`, { timeout: 10000 });
} catch (e) {
logger.debug('No more content to load on this page.');
break;
}
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for images to load
}
let imageCount = 0;
for (const imageUrl of pageImageUrls) {
if (!downloadedUrls.has(imageUrl)) {
downloadedUrls.add(imageUrl);
const pinIdMatch = pinUrl.match(/\/pin\/(\d+)\/?/);
const basePinId = pinIdMatch ? pinIdMatch[1] : `image_${Date.now()}`;
const extension = path.extname(new URL(imageUrl).pathname) || '.jpg';
const filename = `${basePinId}_related_${imageCount++}${extension}`;
const filepath = path.join(downloadPath, filename);
logger.debug(`Downloading ${imageUrl} to ${filepath}`);
await downloadImage(imageUrl, filepath);
downloadedImagePaths.push(filepath);
}
}
} catch (error) {
logger.error(`An error occurred while processing pin ${pinUrl}:`, error);
}
await browser.close();
logger.debug('Done processing pin.');
return downloadedImagePaths;
}

110
src/lib/openai.ts Normal file
View File

@ -0,0 +1,110 @@
import fs from 'fs';
import dotenv from 'dotenv';
import { logger } from './logger';
dotenv.config();
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_MODEL = process.env.OPENAI_MODEL;
async function callOpenAI(prompt: string): Promise<any> {
if (!OPENAI_API_KEY || !OPENAI_MODEL) {
throw new Error('OPENAI_API_KEY or OPENAI_MODEL is not defined in the .env file');
}
for (let i = 0; i < 10; i++) {
let llmResponse = "";
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: OPENAI_MODEL,
messages: [
{
role: 'user',
content: prompt,
},
],
temperature: 0.7,
}),
});
const data = await response.json();
if (data.choices && data.choices.length > 0) {
const content = data.choices[0].message.content;
llmResponse = content;
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
} else {
logger.error('Unexpected API response:', data);
}
} catch (error) {
logger.error(`Attempt ${i + 1} failed:`, error);
logger.debug(`LLM response: ${llmResponse}`)
}
}
throw new Error('Failed to get response from LLM after 10 attempts');
}
async function callOpenAIWithFile(imagePath: string, prompt: string): Promise<any> {
if (!OPENAI_API_KEY || !OPENAI_MODEL) {
throw new Error('OPENAI_API_KEY or OPENAI_MODEL is not defined in the .env file');
}
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = imageBuffer.toString('base64');
for (let i = 0; i < 10; i++) {
let llmResponse = "";
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: OPENAI_MODEL,
messages: [
{
role: 'user',
content: [
{ type: 'image_url', image_url: { url: `data:image/jpeg;base64,${base64Image}` } },
{ type: 'text', text: prompt },
],
},
],
temperature: 0.7,
}),
});
const data = await response.json();
if (data.choices && data.choices.length > 0) {
const content = data.choices[0].message.content;
llmResponse = content;
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
} else {
logger.error('Unexpected API response:', data);
}
} catch (error) {
logger.error(`Attempt ${i + 1} failed:`, error);
logger.debug(`LLM response: ${llmResponse}`)
}
}
throw new Error('Failed to describe image after 10 attempts');
}
export { callOpenAI, callOpenAIWithFile };

View File

@ -0,0 +1,93 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
interface VideoSize {
width: number;
height: number;
}
async function generateStyledVideo(
imagePrompt: string,
videoPrompt: string,
imageName: string,
newFileName: string,
comfyBaseUrl: string,
comfyOutputDir: string,
size: VideoSize = { width: 320, height: 640 }
): Promise<string> {
const COMFY_BASE_URL = comfyBaseUrl.replace(/\/$/, '');
const COMFY_OUTPUT_DIR = comfyOutputDir;
const workflow = JSON.parse(await fs.readFile('src/comfyworkflows/prototyping_style_flux_wan22.json', 'utf-8'));
// Set prompts
workflow['95']['inputs']['text'] = imagePrompt;
workflow['105']['inputs']['text'] = videoPrompt;
// Set image name
workflow['114']['inputs']['image'] = imageName;
// Set sizes
workflow['104']['inputs']['width'] = size.width;
workflow['104']['inputs']['height'] = size.height;
workflow['97']['inputs']['width'] = size.width;
workflow['97']['inputs']['height'] = size.height;
const response = await axios.post(`${COMFY_BASE_URL}/prompt`, { prompt: workflow });
const promptId = response.data.prompt_id;
let history;
do {
await new Promise(resolve => setTimeout(resolve, 1000));
const historyResponse = await axios.get(`${COMFY_BASE_URL}/history/${promptId}`);
history = historyResponse.data[promptId];
} while (!history || Object.keys(history.outputs).length === 0);
const files = await fs.readdir(COMFY_OUTPUT_DIR!);
// Find the latest MP4 file
const generatedVideos = files.filter(file => file.endsWith('.mp4'));
const videoStats = await Promise.all(
generatedVideos.map(async (file) => {
const stat = await fs.stat(path.join(COMFY_OUTPUT_DIR!, file));
return { file, mtime: stat.mtime };
})
);
videoStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
const latestVideo = videoStats[0].file;
// Find the latest PNG file
const generatedImages = files.filter(file => file.endsWith('.png'));
const imageStats = await Promise.all(
generatedImages.map(async (file) => {
const stat = await fs.stat(path.join(COMFY_OUTPUT_DIR!, file));
return { file, mtime: stat.mtime };
})
);
imageStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
const latestImage = imageStats[0].file;
// Create new file paths
const newVideoPath = path.resolve('./generated', newFileName);
const newImagePath = path.resolve('./generated', newFileName.replace('.mp4', '.png'));
await fs.mkdir('./generated', { recursive: true });
// Copy both files
const sourceVideoPath = path.join(COMFY_OUTPUT_DIR!, latestVideo);
await fs.copyFile(sourceVideoPath, newVideoPath);
const sourceImagePath = path.join(COMFY_OUTPUT_DIR!, latestImage);
await fs.copyFile(sourceImagePath, newImagePath);
// Optionally, unlink the source files
// await fs.unlink(sourceVideoPath);
// await fs.unlink(sourceImagePath);
return newVideoPath;
}
export { generateStyledVideo };

View File

@ -0,0 +1,272 @@
import { downloadImagesFromPinterestPin } from './lib/downloader';
import { callOpenAIWithFile } from './lib/openai';
import { generateStyledVideo } from './lib/video-generator-styled';
import { logger } from './lib/logger';
import * as fs from 'fs/promises';
import dotenv from 'dotenv';
import path from 'path';
import puppeteer from 'puppeteer';
import { VideoModel } from './lib/db/video';
dotenv.config();
const servers = [
{
baseUrl: process.env.SERVER1_COMFY_BASE_URL,
outputDir: process.env.SERVER1_COMFY_OUTPUT_DIR,
},
/*
{
baseUrl: process.env.SERVER2_COMFY_BASE_URL,
outputDir: process.env.SERVER2_COMFY_OUTPUT_DIR,
},
*/
].filter((s): s is { baseUrl: string; outputDir: string } => !!s.baseUrl && !!s.outputDir);
interface GenerationTask {
pinUrl: string;
imagePrompt: string;
videoPrompt: string;
imageFileName: string;
renamedImagePath: string;
genre: string;
subGenre: string;
scene: string;
action: string;
camera: string;
}
async function getPromptsForImage(imagePath: string, pinUrl: string, genre: string, subGenre: string): Promise<GenerationTask | null> {
const pinId = pinUrl.split('/').filter(Boolean).pop() || `pin_${Date.now()}`;
const timestamp = new Date().getTime();
const imageFileName = `${pinId}_${timestamp}.png`;
const renamedImagePath = path.join(path.dirname(imagePath), imageFileName);
try {
await fs.rename(imagePath, renamedImagePath);
logger.debug(`Renamed ${imagePath} to ${renamedImagePath}`);
const promptResponse = await callOpenAIWithFile(renamedImagePath,
`Analyze the provided image and generate the following:
1. 'scene': A description of the image's environment.
2. 'action': A description of the main action occurring in the image.
3. 'camera': A description of the camera shot (e.g., 'close-up', 'wide-angle').
4. 'image_prompt': A highly detailed, creative, and artistic prompt for an image generation model, inspired by the original. This prompt should be around 200 words.
5. 'video_prompt': A prompt for an 8-second video, describing a creative and subtle movement of the main object. The camera should be static, but slight panning is acceptable.
Output should be in this JSON format:
---
{
"scene": "{result comes here}",
"action": "{result comes here}",
"camera": "{result comes here}",
"image_prompt": "Ultra detailed illustration, {result comes here}",
"video_prompt": "{result comes here}"
}
---
`);
const { scene, action, camera, image_prompt: imagePrompt, video_prompt: videoPrompt } = promptResponse;
logger.info(`Image prompt for ${renamedImagePath}:`, imagePrompt);
logger.info(`Video prompt for ${renamedImagePath}:`, videoPrompt);
return { pinUrl, imagePrompt, videoPrompt, imageFileName, renamedImagePath, genre, subGenre, scene, action, camera };
} catch (error) {
logger.error(`Failed to get prompts for ${renamedImagePath}:`, error);
try {
await fs.unlink(renamedImagePath);
} catch (cleanupError) {
// ignore
}
return null;
}
}
async function generateImageAndVideo(task: GenerationTask, server: { baseUrl: string; outputDir: string; }): Promise<{ imagePath: string; videoPath: string; } | null> {
const { imagePrompt, videoPrompt, imageFileName, renamedImagePath } = task;
const { baseUrl, outputDir } = server;
const inputDir = outputDir.replace("output", "input");
try {
const destPath = path.join(inputDir, imageFileName);
await fs.copyFile(renamedImagePath, destPath);
logger.info(`Copied ${renamedImagePath} to ${destPath}`);
const videoFileName = imageFileName.replace('.png', '.mp4');
await generateStyledVideo(
imagePrompt,
videoPrompt,
imageFileName,
videoFileName,
baseUrl,
outputDir,
{ width: 320, height: 640 }
);
const videoPath = path.join(outputDir, videoFileName);
return { imagePath: destPath, videoPath };
} catch (error) {
logger.error(`Failed to generate styled video for ${imageFileName} on server ${baseUrl}:`, error);
return null;
} finally {
try {
await fs.unlink(renamedImagePath);
logger.debug(`Deleted renamed source image: ${renamedImagePath}`);
} catch (error) {
logger.error(`Failed to delete renamed source image ${renamedImagePath}:`, error);
}
}
}
async function worker(id: number, server: { baseUrl: string; outputDir: string; }, taskQueue: GenerationTask[]) {
logger.info(`Worker ${id} started for server ${server.baseUrl}`);
while (taskQueue.length > 0) {
const task = taskQueue.shift();
if (task) {
logger.info(`Worker ${id} processing task: ${task.imageFileName}`);
const result = await generateImageAndVideo(task, server);
if (result) {
try {
const videoData = {
genre: task.genre,
sub_genre: task.subGenre,
scene: task.scene,
action: task.action,
camera: task.camera,
image_prompt: task.imagePrompt,
video_prompt: task.videoPrompt,
image_path: result.imagePath,
video_path: result.videoPath,
};
const videoId = await VideoModel.create(videoData);
logger.info(`Successfully saved video record to database with ID: ${videoId}`);
const newImageName = `${videoId}_${task.genre}_${task.subGenre}${path.extname(result.imagePath)}`;
const newVideoName = `${videoId}_${task.genre}_${task.subGenre}${path.extname(result.videoPath)}`;
const newImagePath = path.join(path.dirname(result.imagePath), newImageName);
const newVideoPath = path.join(path.dirname(result.videoPath), newVideoName);
await fs.rename(result.imagePath, newImagePath);
await fs.rename(result.videoPath, newVideoPath);
await VideoModel.update(videoId, {
image_path: newImagePath,
video_path: newVideoPath,
});
logger.info(`Renamed files and updated database record for video ID: ${videoId}`);
} catch (error) {
logger.error('Failed to save video record to database or rename files:', error);
}
}
}
}
logger.info(`Worker ${id} finished.`);
}
async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36');
await page.setViewport({ width: 1920, height: 1080 });
try {
const searchUrl = `https://www.pinterest.com/search/pins/?q=${encodeURIComponent(keyword)}`;
await page.goto(searchUrl, { waitUntil: 'networkidle2' });
const scrollCount = Math.floor(Math.random() * 5) + 1;
logger.info(`Scrolling ${scrollCount} times...`);
for (let i = 0; i < scrollCount; i++) {
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000 + 1000));
}
const pinLinks = await page.$$eval('a', (anchors) =>
anchors.map((a) => a.href).filter((href) => href.includes('/pin/'))
);
if (pinLinks.length > 0) {
return pinLinks[Math.floor(Math.random() * pinLinks.length)];
}
return null;
} catch (error) {
logger.error('Error while getting pin URL from Pinterest:', error);
return null;
} finally {
await browser.close();
}
}
(async () => {
const keywords = [
{ genre: "fantasy", subGenre: "ethereal" },
{ genre: "fantasy", subGenre: "academia" },
{ genre: "fantasy", subGenre: "darkacademia" },
{ genre: "fantasy", subGenre: "illumication" },
{ genre: "fantasy", subGenre: "aesthetic" },
{ genre: "abstract", subGenre: "particle" },
{ genre: "abstract", subGenre: "space" },
{ genre: "abstract", subGenre: "science" },
{ genre: "abstract", subGenre: "sphere" },
{ genre: "abstract", subGenre: "cubic" },
];
if (servers.length === 0) {
logger.error("No servers configured. Please check your .env file.");
return;
}
while (true) {
for (const genreSubGenre of keywords) {
const { genre, subGenre } = genreSubGenre;
const keyword = `${genre} ${subGenre}`;
logger.info(`Searching for a pin with keyword: ${keyword}`);
const pin = await getPinUrlFromPinterest(keyword);
if (!pin) {
logger.warn(`Could not find a pin for keyword: ${keyword}. Skipping.`);
continue;
}
const generationTasks: GenerationTask[] = [];
logger.info(`--- Starting processing for pin: ${pin} ---`);
const downloadedImagePaths = await downloadImagesFromPinterestPin(pin);
if (downloadedImagePaths.length === 0) {
logger.warn(`No images were downloaded for pin ${pin}. Skipping.`);
continue;
}
const selectedImages = downloadedImagePaths.sort(() => 0.5 - Math.random()).slice(0, 1);
logger.info(`--- Randomly selected ${selectedImages.length} images for processing ---`);
for (const imagePath of selectedImages) {
const pinId = path.basename(imagePath, path.extname(imagePath)).split('_related_')[0];
const pinUrl = `https://www.pinterest.com/pin/${pinId}/`;
const task = await getPromptsForImage(imagePath, pinUrl, genre, subGenre);
if (task) {
generationTasks.push(task);
}
}
const unselectedImages = downloadedImagePaths.filter(p => !selectedImages.includes(p));
for (const imagePath of unselectedImages) {
try {
await fs.unlink(imagePath);
logger.debug(`Deleted unselected image: ${imagePath}`);
} catch (error) {
logger.error(`Failed to delete unselected image ${imagePath}:`, error);
}
}
if (generationTasks.length > 0) {
logger.info(`--- Starting parallel generation of ${generationTasks.length} tasks across ${servers.length} servers ---`);
const workers = servers.map((server, index) => worker(index + 1, server, generationTasks));
await Promise.all(workers);
logger.info("--- Finished parallel generation ---");
}
}
}
})();