Compare commits

..
18 Commits
Author SHA1 Message Date
shadow 59f654fa39 Merge pull request #341 from shadowcz007/SenseVoice
Sense voice
2024-10-01 23:17:37 +08:00
shadowcz007 be8ccc1dc4 新增 SenseVoice 2024-10-01 23:16:02 +08:00
shadowcz007 228e5d9183 Update SenseVoice.py 2024-10-01 23:13:31 +08:00
shadowcz007 8afe6d0383 update 2024-10-01 22:15:35 +08:00
shadowcz007 5f7190b08f Update SenseVoice.py 2024-10-01 21:01:27 +08:00
shadowcz007 a70a9b4bb1 update 2024-10-01 20:41:05 +08:00
shadowcz007 b796e66890 Create SenseVoice.py 2024-10-01 17:57:54 +08:00
shadow f1a663779a Update README.md 2024-09-27 17:49:37 +08:00
shadowcz007 b0aa972326 Update index.html 2024-09-24 15:05:36 +08:00
shadowcz007 ef927a7ed1 loadimage from path ,优化 排序逻辑 ,增加 sort_by_filename 2024-09-23 10:37:42 +08:00
shadowcz007 aa8fc59051 scenedetect & createJSON & PromptImage
- 优化从视频提取片段,并输出json保存
2024-09-22 21:35:00 +08:00
shadowcz007 ce62204392 Update PromptNode.py 2024-09-22 19:30:35 +08:00
shadowcz007 837f28142d Update extension-node-map.json 2024-09-21 21:17:32 +08:00
shadowcz007 60c79c991d Qwen2.5 2024-09-20 12:20:27 +08:00
shadowcz007 078aaeb679 depth viewer 2024-09-18 18:34:55 +08:00
shadowcz007 d9edbd535e 适配不同前端版本 2024-09-18 15:37:16 +08:00
shadowcz007 b4a61b21c3 Update td_background.js 2024-09-18 15:25:01 +08:00
shadowcz007 bdc4193ffe - 新增API调用图像生成节点 TextToImage Siliconflow,可以直接调用Siliconflow提供的flux生成图像
v0.42.0
2024-09-18 09:45:57 +08:00
16 changed files with 1100 additions and 124 deletions
+6
View File
@@ -10,6 +10,12 @@ For business cooperation, please contact email 389570357@qq.com
##### `最新`:
- 新增 SenseVoice
- [新增JS-SDK,方便直接在前端项目中使用comfyui](https://github.com/shadowcz007/comfyui-js-sdk)
- 新增API调用图像生成节点 TextToImage Siliconflow,可以直接调用Siliconflow提供的flux生成图像
- [增加 Her 的DEMO页面,和数字人对话](https://github.com/shadowcz007/ComfyUI-Backend-MixlabNodes/blob/main/workflow/her_demo_workflow.json)
- 右键菜单支持 text-to-text,方便对 prompt 词补全,支持云LLM或者是本地LLM。
+12 -1
View File
@@ -1007,7 +1007,7 @@ from .nodes.ImageNode import DepthViewer_,ImageBatchToList_,ImageListToBatch_,Co
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
from .nodes.Audio import AudioPlayNode,SpeechRecognition,SpeechSynthesis
from .nodes.Utils import KeyInput,IncrementingListNode,ListSplit,CreateLoraNames,CreateSampler_names,CreateCkptNames,CreateSeedNode,TESTNODE_,TESTNODE_TOKEN,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
from .nodes.Utils import CreateJsonNode,KeyInput,IncrementingListNode,ListSplit,CreateLoraNames,CreateSampler_names,CreateCkptNames,CreateSeedNode,TESTNODE_,TESTNODE_TOKEN,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
from .nodes.Mask import PreviewMask_,MaskListReplace,MaskListMerge,OutlineMask,FeatheredMask
from .nodes.Style import ApplyVisualStylePrompting,StyleAlignedReferenceSampler,StyleAlignedBatchAlign,StyleAlignedSampleReferenceLatents
@@ -1045,6 +1045,7 @@ NODE_CLASS_MAPPINGS = {
"SaveImageToLocal":SaveImageToLocal,
"SaveImageAndMetadata_":SaveImageAndMetadata,
"ComparingTwoFrames_":ComparingTwoFrames,
"CreateJsonNode":CreateJsonNode,
# Image
"MirroredImage":MirroredImage,
@@ -1225,6 +1226,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"MultiplicationNode":"Math Operation ♾️Mixlab",
"ListSplit_":"Split List ♾️Mixlab",
"SwitchByIndex":"List Switch By Index ♾️Mixlab",
"CreateJsonNode":"Create Json",
# "GamePal":"GamePal ♾️Mixlab",
# Experiment
@@ -1427,6 +1429,15 @@ try:
except Exception as e:
logging.info('FishSpeech.available False' )
try:
from .nodes.SenseVoice import SenseVoiceNode
logging.info('SenseVoice.available')
NODE_CLASS_MAPPINGS['SenseVoiceNode']=SenseVoiceNode
NODE_DISPLAY_NAME_MAPPINGS["SenseVoiceNode"]= "Sense Voice"
except Exception as e:
logging.info('SenseVoice.available False' )
logging.info('\033[93m -------------- \033[0m')
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -460,7 +460,8 @@ class SiliconflowFreeNode:
@classmethod
def INPUT_TYPES(cls):
model_list= [
"Qwen/Qwen2-7B-Instruct",
"Qwen/Qwen2.5-7B-Instruct",
"Qwen/Qwen2-7B-Instruct",
"THUDM/glm-4-9b-chat",
"01-ai/Yi-1.5-9B-Chat-16K",
"meta-llama/Meta-Llama-3.1-8B-Instruct"
+30 -13
View File
@@ -13,12 +13,26 @@ import json,io
import comfy.utils
from comfy.cli_args import args
import cv2
import string
import string,re
import math,glob
from .Watcher import FolderWatcher
from itertools import product
# 文件名排序
def sort_by_filename(items):
def extract_parts(filename):
# 使用正则表达式将文件名拆分为数字和非数字部分
parts = re.split(r'(\d+)', filename)
# 将数字部分转换为整数以便正确排序,同时保留非数字部分
parts = [int(part) if part.isdigit() else part for part in parts]
return parts
# 按照 file_name 的拆分部分进行排序
sorted_items = sorted(items, key=lambda x: extract_parts(x['file_name']))
return sorted_items
# 将PIL图片转换为OpenCV格式
def pil_to_opencv(image):
open_cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
@@ -768,8 +782,7 @@ def areaToMask(x,y,w,h,image):
# return bg_image
import cv2
import numpy as np
# ps的正片叠底
# 可以基于https://www.cnblogs.com/jsxyhelu/p/16947810.html ,用gpt写python代码
@@ -1433,7 +1446,7 @@ class LoadImagesFromPath:
},
"optional":{
"white_bg": (["disable","enable"],),
"newest_files": (["enable", "disable"],),
"sort_by": (["file_name", "newest"],),#根据文件名来排序,还是按照最新创建时间
"index_variable":("INT", {
"default": 0,
"min": -1, #Minimum value
@@ -1462,7 +1475,7 @@ class LoadImagesFromPath:
watcher_folder=None
# 运行的函数
def run(self,file_path,white_bg,newest_files,index_variable,watcher,result,prompt,seed=1):
def run(self,file_path,white_bg,sort_by,index_variable,watcher,result,prompt,seed=1):
global watcher_folder
# print('###监听:',watcher_folder,watcher,file_path,result)
@@ -1485,19 +1498,23 @@ class LoadImagesFromPath:
# 当开启了监听,则取最新的,第一个文件
if watcher=='enable':
index_variable=0
newest_files='enable'
sort_by='newest'
# 排序
sorted_files = sorted(images, key=lambda x: os.path.getmtime(x['file_path']), reverse=(newest_files=='enable'))
if sort_by=='newest':
sorted_files = sorted(images, key=lambda x: os.path.getmtime(x['file_path']), reverse=True)
elif sort_by=='file_name':
# 根据文件名排序
sorted_files = sort_by_filename(images)
imgs=[]
masks=[]
file_names=[]
file_paths=[]
for im in sorted_files:
imgs.append(im['image'])
masks.append(im['mask'])
file_names.append(im['file_name'])
file_paths.append(im['file_path'])
# print('index_variable',index_variable)
@@ -1505,13 +1522,13 @@ class LoadImagesFromPath:
if index_variable!=-1:
imgs=[imgs[index_variable]] if index_variable < len(imgs) else None
masks=[masks[index_variable]] if index_variable < len(masks) else None
file_names=[file_names[index_variable]] if index_variable < len(file_names) else None
file_paths=[file_paths[index_variable]] if index_variable < len(file_paths) else None
except Exception as e:
print("发生了一个未知的错误:", str(e))
# print('#prompt::::',prompt)
# return {"ui": {"seed": [1]}, "result":(imgs,masks,prompt,file_names,)}
return (imgs,masks,prompt,file_names,)
return (imgs,masks,prompt,file_paths,)
# TODO 扩大选区的功能,重新输出mask
@@ -2873,14 +2890,14 @@ class ResizeImage:
"default": 512,
"min": 1, #Minimum value
"max": 8192, #Maximum value
"step": 8, #Slider's step
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"height": ("INT",{
"default": 512,
"min": 1, #Minimum value
"max": 8192, #Maximum value
"step": 8, #Slider's step
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"scale_option": (["width","height",'overall','center'],),
+26 -6
View File
@@ -187,7 +187,8 @@ class PromptImage:
}
}
RETURN_TYPES = ()
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("json_str",)
OUTPUT_NODE = True
@@ -202,12 +203,19 @@ class PromptImage:
filename_prefix="mixlab_"
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
filename_prefix,self.output_dir, images[0].shape[1], images[0].shape[0])
full_output_folder=os.path.join(full_output_folder,'PromptImage')
subfolder='PromptImage'
results = list()
save_to_image=save_to_image[0]=='enable'
#保存到本地的json文件,记录图片和prompt的对应关系
output_images=[]
output_prompt=[]
for index in range(len(images)):
res=[]
imgs=images[index]
@@ -215,24 +223,36 @@ class PromptImage:
for image in imgs:
img=tensor2pil(image)
prompt_text=prompts[index]
metadata = None
if save_to_image:
metadata = PngInfo()
prompt_text=prompts[index]
if prompt_text is not None:
metadata.add_text("prompt_text", prompt_text)
file = f"{filename}_{index}_{counter:05}_.png"
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
fp=os.path.join(full_output_folder,file)
img.save(fp, pnginfo=metadata, compress_level=self.compress_level)
res.append({
"filename": file,
"subfolder": subfolder,
"type": self.type
})
output_images.append(fp)
output_prompt.append(prompt_text)
counter += 1
results.append(res)
return { "ui": { "_images": results,"prompts":prompts } }
# if save_to_image:
# # 保存为本地文件
# with open(os.path.join(full_output_folder,'PromptImage.json'), 'w') as file:
# json.dump(output_dict, file, ensure_ascii=False, indent=4)
return { "ui": { "_images": results,"prompts":prompts },"result":(json.dumps({
"images":output_images,
"prompts":output_prompt
}),) }
+215
View File
@@ -0,0 +1,215 @@
# -*- coding:utf-8 -*-
import logging
import os
import time
from huggingface_hub import snapshot_download
import torch,re
from sensevoice.onnx.sense_voice_ort_session import SenseVoiceInferenceSession
from sensevoice.utils.frontend import WavFrontend
from sensevoice.utils.fsmn_vad import FSMNVad
import comfy.utils
import folder_paths
languages = {"auto": 0, "zh": 3, "en": 4, "yue": 7, "ja": 11, "ko": 12, "nospeech": 13}
# 设置环境变量
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
#
def get_model_path():
try:
return folder_paths.get_folder_paths('sense_voice')[0]
except:
return os.path.join(folder_paths.models_dir, "sense_voice")
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
any_type = AnyType("*")
# 字幕
def format_to_srt(channel_id, start_time_ms, end_time_ms, asr_result):
start_time = start_time_ms / 1000
end_time = end_time_ms / 1000
def format_time(seconds):
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
seconds = seconds % 60
milliseconds = int((seconds - int(seconds)) * 1000)
return f"{hours:02}:{minutes:02}:{int(seconds):02},{milliseconds:03}"
start_time_str = format_time(start_time)
end_time_str = format_time(end_time)
pattern = r"<\|(.+?)\|><\|(.+?)\|><\|(.+?)\|><\|(.+?)\|>(.+)"
match = re.match(pattern,asr_result)
lang, emotion, audio_type, itn, text = match.groups()
# 😊 表示高兴,😡 表示愤怒,😔 表示悲伤。对于音频事件,🎼 表示音乐,😀 表示笑声,👏 表示掌声
srt_content = f"1\n{start_time_str} --> {end_time_str}\n{text}\n"
logging.info(f"[Channel {channel_id}] [{start_time}s - {end_time}s] [{lang}] [{emotion}] [{audio_type}] [{itn}] {text}")
return lang, emotion, audio_type, itn,srt_content,start_time,end_time,text
class SenseVoiceProcessor:
def __init__(self, download_model_path, device, num_threads, use_int8):
if not os.path.exists(download_model_path):
logging.info(
"Downloading model from huggingface hub from https://huggingface.co/lovemefan/SenseVoice-onnx"
)
logging.info(
"You can speed up with `export HF_ENDPOINT=https://hf-mirror.com`"
)
snapshot_download(
repo_id="lovemefan/SenseVoice-onnx", local_dir=download_model_path
)
self.download_model_path = download_model_path
self.device = device
self.num_threads = num_threads
self.use_int8 = use_int8
self.front = WavFrontend(os.path.join(download_model_path, "am.mvn"))
self.model = SenseVoiceInferenceSession(
os.path.join(download_model_path, "embedding.npy"),
os.path.join(
download_model_path,
"sense-voice-encoder-int8.onnx"
if use_int8
else "sense-voice-encoder.onnx",
),
os.path.join(download_model_path, "chn_jpn_yue_eng_ko_spectok.bpe.model"),
device,
num_threads,
)
self.vad = FSMNVad(download_model_path)
def process_audio(self, waveform, _sample_rate, language, use_itn):
start = time.time()
pbar = comfy.utils.ProgressBar(waveform.shape[1]) # 进度条
results = []
for channel_id, channel_data in enumerate(waveform.T):
segments = self.vad.segments_offline(channel_data)
for part in segments:
audio_feats = self.front.get_features(channel_data[part[0] * 16 : part[1] * 16])
asr_result = self.model(
audio_feats[None, ...],
language=languages[language],
use_itn=use_itn,
)
lang, emotion, audio_type, itn,srt_content,start_time,end_time,text=format_to_srt(
channel_id,
part[0] ,
part[1],
asr_result)
results.append({
"language":lang,
"emotion":emotion,
"audio_type":audio_type,
"itn":itn,
"srt_content":srt_content,
"start_time":start_time,
"end_time":end_time,
"text":text
})
self.vad.vad.all_reset_detection()
pbar.update(1) # 更新进度条
decoding_time = time.time() - start
logging.info(f"Decoder audio takes {decoding_time} seconds")
logging.info(f"The RTF is {decoding_time/(waveform.shape[1] * len(waveform) / _sample_rate)}.")
return results
class SenseVoiceNode:
def __init__(self):
self.processor = None
self.download_model_path=get_model_path()
self.device="cpu"
self.num_threads = 4
self.use_int8 = True
self.language='auto'
@classmethod
def INPUT_TYPES(s):
return {"required": {
"audio": ("AUDIO", ),
"device": ( ['auto','cpu'], {"default": 'auto'}),
"language": (list(languages.keys()), {"default": 'auto'}),# 不能直接写 languages.keys(),json.dumps会报错
"num_threads":("INT",{
"default":4,
"min": 1, #Minimum value
"max": 32, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
},),
"use_int8":("BOOLEAN", {"default": True},),
"use_itn":("BOOLEAN", {"default": True},),
},
}
CATEGORY = "♾️Mixlab/Audio"
OUTPUT_NODE = True
FUNCTION = "run"
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("result",)
def run(self,audio,device,language,num_threads,use_int8,use_itn ):
if device!=self.device:
self.device=device
self.processor=None
if language!=self.language:
self.language=language
self.processor=None
if num_threads!=self.num_threads:
self.num_threads=num_threads
self.processor=None
if use_int8!=self.use_int8:
self.use_int8=use_int8
self.processor=None
if device=='auto' and torch.cuda.is_available():
self.device='cuda'
# num_threads=4
# use_int8=True
if self.processor==None:
self.processor = SenseVoiceProcessor(self.download_model_path,
self.device,
self.num_threads,
self.use_int8)
if 'waveform' in audio and 'sample_rate' in audio:
waveform = audio['waveform']
# print("Original shape:", waveform.shape) # 打印原始形状
if waveform.ndim == 3 and waveform.shape[0] == 1: # 检查是否为三维且 batch_size 为 1
waveform = waveform.squeeze(0) # 移除 batch_size 维度
waveform_numpy = waveform.numpy().transpose(1, 0) # 转换为 (num_samples, num_channels)
else:
raise ValueError("Unexpected waveform dimensions")
_sample_rate = audio['sample_rate']
results=self.processor.process_audio(waveform_numpy, _sample_rate, language, use_itn)
return (results,)
+43
View File
@@ -648,6 +648,49 @@ class AppInfo:
class CreateJsonNode:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"key": ("STRING",{"multiline": False,"default": "data","dynamicPrompts": False}),
"value":(any_type,),
"save":("BOOLEAN", {"default": True},),
},
"optional":{
"json_str":("STRING", {"forceInput": True,}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("json_str",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Output"
OUTPUT_NODE = True
INPUT_IS_LIST = False
# OUTPUT_IS_LIST = (True,)
def run(self,key,value,save,json_str=None):
data={}
data[key]=value
if json_str:
json_obj = json.loads(json_str)
data.update(json_obj)
if save:
# 保存为本地文件
with open(os.path.join(folder_paths.get_output_directory(),'data.json'), 'w') as file:
json.dump(data, file, ensure_ascii=False, indent=4)
return (json.dumps(data),)
class SwitchByIndex:
+8 -5
View File
@@ -132,6 +132,9 @@ def split_video_by_scenes(video_path, scenes, output_path, number_of_sample_fram
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 视频的总帧数
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
# Create a list to hold the paths of the scene videos
scenes_video = []
keyframes = []
@@ -194,7 +197,7 @@ def split_video_by_scenes(video_path, scenes, output_path, number_of_sample_fram
# Release the video file
video.release()
return scenes_video, keyframes
return scenes_video, keyframes,total_frames
def get_files_with_extension(directory, extension):
@@ -287,8 +290,8 @@ class ScenedetectNode_:
"number_of_sample_frames": ("INT", {"default": 1, "min": 1, "step": 1}), # 抽取的帧数,默认是1帧,中间帧
},}
RETURN_TYPES = ("SCENE_VIDEO","SCENE_", "INT",)
RETURN_NAMES = ("scenes_video","scenes","scene_len",)
RETURN_TYPES = ("SCENE_VIDEO","SCENE_", "INT","INT",)
RETURN_NAMES = ("scenes_video","scenes","scene_len","total_frames",)
OUTPUT_IS_LIST = (False,False,False,)
FUNCTION = "run"
@@ -310,9 +313,9 @@ class ScenedetectNode_:
folder_path = create_folder(tp,name_without_extension)
# print("New folder created:", folder_path)
vs_files,keyframes=split_video_by_scenes(video_path,scenes,folder_path,number_of_sample_frames)
vs_files,keyframes,total=split_video_by_scenes(video_path,scenes,folder_path,number_of_sample_frames)
# print("New folder created:", vs_files)
return (vs_files,keyframes,len(scenes),)
return (vs_files,keyframes,len(scenes),total,)
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-mixlab-nodes"
description = "3D, ScreenShareNode & FloatingVideoNode, SpeechRecognition & SpeechSynthesis, GPT, LoadImagesFromLocal, Layers, Other Nodes, ..."
version = "0.41.1"
version = "0.43.0"
license = "MIT"
dependencies = ["numpy", "pyOpenSSL", "watchdog", "opencv-python-headless", "matplotlib", "openai", "simple-lama-inpainting", "clip-interrogator==0.6.0", "transformers>=4.36.0", "lark-parser", "imageio-ffmpeg", "rembg[gpu]", "omegaconf==2.3.0", "Pillow>=9.5.0", "einops==0.7.0", "trimesh>=4.0.5", "huggingface-hub", "scikit-image"]
+1 -1
View File
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version = 'v0.41.1'
const version = 'v0.43.0'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+5 -1
View File
@@ -6,6 +6,10 @@ window._bg_img = null
* draws the back canvas (the one containing the background and the connections)
* @method drawBackCanvas
**/
// 判断是否是新版的,LGraphCanvas.prototype.drawBackCanvas.toString().match('window.devicePixelRatio')
let scale=LGraphCanvas.prototype.drawBackCanvas.toString().match('window.devicePixelRatio')?window.devicePixelRatio:1;
LGraphCanvas.prototype.drawBackCanvas = function () {
var canvas = this.bgcanvas
if (
@@ -60,7 +64,7 @@ LGraphCanvas.prototype.drawBackCanvas = function () {
if (!this.viewport) {
ctx.restore()
// ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.setTransform(window.devicePixelRatio, 0, 0, window.devicePixelRatio, 0, 0)
ctx.setTransform(scale, 0, 0, scale, 0, 0)
}
this.visible_links.length = 0
+15 -11
View File
@@ -41,6 +41,9 @@ class Visualizer {
overflow: 'hidden'
})
this.iframe.src = '/mixlab/app/' + visualSrc + '.html'
// this.iframe.width="300";
// this.iframe.height="400";
console.log('#Visualizer', container, this.iframe)
container.appendChild(this.iframe)
}
@@ -73,7 +76,7 @@ function createVisualizer (node, inputName, typeName, inputData, app) {
draw: function (ctx, node, widgetWidth, widgetY, widgetHeight) {
const margin = 10
const top_offset = 5
const visible = app.canvas.ds.scale > 0.5 && this.type === typeName
const visible = app.canvas.ds.scale > 0.3 && this.type === typeName
const w = widgetWidth - margin * 4
const clientRectBound = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
@@ -85,12 +88,13 @@ function createVisualizer (node, inputName, typeName, inputData, app) {
.translateSelf(margin, margin + widgetY)
Object.assign(this.visualizer.style, {
left: `${transform.a * margin + transform.e + 40}px`,
left: `${transform.a * margin + transform.e + 0}px`,
top: `${transform.d + transform.f + top_offset}px`,
width: `${w * transform.a}px`,
height: `${
w * transform.d - widgetHeight - margin * 15 * transform.d
}px`,
height: `${(w * transform.a * 4) / 3 - margin * 5 * transform.d}px`,
// height: `${
// w * transform.d - widgetHeight - margin * 15 * transform.d
// }px`,
position: 'absolute',
overflow: 'hidden',
zIndex: app.graph._nodes.indexOf(node)
@@ -137,11 +141,11 @@ function createVisualizer (node, inputName, typeName, inputData, app) {
// Make sure visualization iframe is always inside the node when resize the node
node.onResize = function () {
let [w, h] = this.size
if (w <= 600) w = 600
if (h <= 500) h = 500
if (w <= 300) w = 300
if (h <= 400) h = 400
if (w > 600) {
h = w - 100
if (w > 300) {
h = Math.round((w * 4) / 3)
}
this.size = [w, h]
@@ -181,14 +185,14 @@ function registerVisualizer (nodeType, nodeData, nodeClassName, typeName) {
app
])
this.setSize([600, 500])
this.setSize([300, 400])
return r
}
nodeType.prototype.onExecuted = async function (message) {
// Check if reference image and depth map are available
console.log("#message",message)
console.log('#message', message)
if (message.reference_image && message.depth_map) {
const params = {}
params.reference_image = message.reference_image[0]
+2 -2
View File
@@ -2026,8 +2026,8 @@
var iframe = document.createElement('iframe')
iframe.src = "https://mememagic-editor.vercel.app/"
iframe.setAttribute('frameborder', '0')
iframe.setAttribute('width', '500')
iframe.setAttribute('height', '700')
iframe.setAttribute('width', '700')
iframe.setAttribute('height', '720')
iframe.setAttribute('allow',"clipboard-read; clipboard-write")
+4
View File
@@ -223,6 +223,10 @@
margin-top: 0;
}
.image-with-grid img {
margin: 0 !important;
}
/* .card:hover {
box-shadow: 0px 0px 10px 10px #e9fbfa;
} */
+27 -2
View File
@@ -237,8 +237,11 @@ const sleep = (t = 1000) => {
// 方法:旋转摄像机并拍摄图片 // 每次旋转的角度增量,转换为弧度
async function captureImages (
totalFrames = 20,
angleIncrement = THREE.MathUtils.degToRad(1.5)
angleIncrement = 1.5,
scaleFactor = 1 // 添加放大倍数参数,默认为1
) {
angleIncrement = THREE.MathUtils.degToRad(angleIncrement)
// 计算场景中所有物体的中心点
const box = new THREE.Box3().setFromObject(scene)
const center = new THREE.Vector3()
@@ -264,6 +267,21 @@ async function captureImages (
const startAngle = initialAngle
// - (angleIncrement * totalFrames) / 2
// 保存原始尺寸
const originalWidth = renderer.domElement.width
const originalHeight = renderer.domElement.height
// 调整渲染器尺寸
renderer.setSize(
originalWidth * scaleFactor,
originalHeight * scaleFactor,
false
)
// 调整相机的视图矩阵(如果需要)
camera.aspect = (originalWidth * scaleFactor) / (originalHeight * scaleFactor)
camera.updateProjectionMatrix()
for (let i = 0; i < totalFrames; i++) {
const angle = startAngle + i * angleIncrement
@@ -284,6 +302,13 @@ async function captureImages (
await new Promise(resolve => setTimeout(resolve, 500))
}
// 恢复渲染器尺寸
renderer.setSize(originalWidth, originalHeight, false)
// 恢复相机的视图矩阵
camera.aspect = originalWidth / originalHeight
camera.updateProjectionMatrix()
// 恢复相机到初始位置和朝向
camera.position.copy(initialPosition)
camera.lookAt(initialTarget)
@@ -294,7 +319,7 @@ async function captureImages (
async function takeScreenshot () {
// 更新相机的矩阵,以确保其世界矩阵是最新的
camera.updateMatrixWorld()
const imgs = await captureImages()
const imgs = await captureImages(12,3,4)
// 获取当前网页的 URL
const currentUrl = window.location.href