176 lines
5.5 KiB
Python
176 lines
5.5 KiB
Python
from transformers import (
|
|
Qwen2_5_VLForConditionalGeneration,
|
|
AutoProcessor
|
|
)
|
|
# import torch
|
|
from pathlib import Path
|
|
import folder_paths
|
|
import os
|
|
|
|
# from .logger import logger
|
|
from .utils import calculate_file_hash, strip_path
|
|
|
|
# .\python_embeded\python.exe -m pip install qwen_vl_utils
|
|
from qwen_vl_utils import process_vision_info
|
|
|
|
# https://huggingface.co/OpenGVLab/VideoChat-R1_7B/tree/main
|
|
# https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite
|
|
class ModelLoader:
|
|
def __init__(self):
|
|
self.model_checkpoint = None
|
|
self.model = None
|
|
self.processor = None
|
|
# self.device = (
|
|
# torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
|
|
# )
|
|
# self.bf16_support = (
|
|
# torch.cuda.is_available()
|
|
# and torch.cuda.get_device_capability(self.device)[0] >= 8
|
|
# )
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model": (["VideoChat-R1_7B", ],{"default": "VideoChat-R1_7B"},),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("MODEL", "PROCESSOR")
|
|
RETURN_NAMES = ("model", "processor")
|
|
FUNCTION = "load_model"
|
|
CATEGORY = "VideoChatWrapper"
|
|
|
|
def load_model(self, model):
|
|
model_id = f"OpenGVLab/{model}"
|
|
# put downloaded model to model/videochat dir
|
|
self.model_checkpoint = os.path.join(
|
|
folder_paths.models_dir, "videochat", os.path.basename(model_id)
|
|
)
|
|
if not os.path.exists(self.model_checkpoint):
|
|
from huggingface_hub import snapshot_download
|
|
|
|
snapshot_download(
|
|
repo_id=model_id,
|
|
local_dir=self.model_checkpoint,
|
|
local_dir_use_symlinks=False,
|
|
)
|
|
if self.model is None:
|
|
self.model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
|
self.model_checkpoint,
|
|
torch_dtype="auto",
|
|
device_map="auto",
|
|
# attn_implementation="flash_attention_2"
|
|
)
|
|
self.processor = AutoProcessor.from_pretrained(self.model_checkpoint)
|
|
return self.model, self.processor
|
|
|
|
|
|
class VideoSummary:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model": ("MODEL",),
|
|
"processor": ("PROCESSOR",),
|
|
"video_path": ("STRING", ),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("text",)
|
|
FUNCTION = "video_summary"
|
|
CATEGORY = "VideoChatWrapper"
|
|
|
|
def video_summary(self, model, processor, video_path):
|
|
question = "Describe this video in detail."
|
|
messages = [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "video",
|
|
"video": video_path,
|
|
"max_pixels": 360 * 420,
|
|
"fps": 1.0,
|
|
},
|
|
{"type": "text", "text": f"""{question}
|
|
"""},
|
|
],
|
|
}
|
|
]
|
|
|
|
#In Qwen 2.5 VL, frame rate information is also input into the model to align with absolute time.
|
|
# Preparation for inference
|
|
text = processor.apply_chat_template(
|
|
messages, tokenize=False, add_generation_prompt=True
|
|
)
|
|
image_inputs, video_inputs, video_kwargs = process_vision_info(messages, return_video_kwargs=True)
|
|
inputs = processor(
|
|
text=[text],
|
|
images=image_inputs,
|
|
videos=video_inputs,
|
|
padding=True,
|
|
return_tensors="pt",
|
|
**video_kwargs,
|
|
)
|
|
inputs = inputs.to("cuda")
|
|
|
|
# Inference
|
|
generated_ids = model.generate(**inputs, max_new_tokens=512)
|
|
generated_ids_trimmed = [
|
|
out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
|
|
]
|
|
output_text = processor.batch_decode(
|
|
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
|
|
)
|
|
return (output_text,)
|
|
|
|
video_extensions = ['webm', 'mp4', 'mkv', 'mov']
|
|
|
|
def load_video(**kwargs):
|
|
kwargs['video'] = strip_path(kwargs['video'])
|
|
# print("video_path: ", kwargs['video'])
|
|
return (kwargs['video'],)
|
|
|
|
class LoadVideoUpload:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
input_dir = folder_paths.get_input_directory()
|
|
files = []
|
|
for f in os.listdir(input_dir):
|
|
if os.path.isfile(os.path.join(input_dir, f)):
|
|
file_parts = f.split('.')
|
|
if len(file_parts) > 1 and (file_parts[-1].lower() in video_extensions):
|
|
files.append(f)
|
|
return {"required": {
|
|
"video": (sorted(files),),
|
|
},
|
|
}
|
|
|
|
CATEGORY = "VideoChatWrapper"
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("video_path",)
|
|
FUNCTION = "load_video"
|
|
|
|
def load_video(self, **kwargs):
|
|
kwargs['video'] = folder_paths.get_annotated_filepath(strip_path(kwargs['video']))
|
|
return load_video(**kwargs)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(s, video, **kwargs):
|
|
image_path = folder_paths.get_annotated_filepath(video)
|
|
return calculate_file_hash(image_path)
|
|
|
|
@classmethod
|
|
def VALIDATE_INPUTS(s, video):
|
|
if not folder_paths.exists_annotated_filepath(video):
|
|
return "Invalid video file: {}".format(video)
|
|
return True
|
|
|
|
|