vibe-aigc 0.6.1__tar.gz → 0.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vibe_aigc-0.6.1/vibe_aigc.egg-info → vibe_aigc-0.6.3}/PKG-INFO +1 -1
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/pyproject.toml +1 -1
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/tools.py +14 -2
- vibe_aigc-0.6.3/vibe_aigc/tools_comfyui.py +271 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/vibe_backend.py +197 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3/vibe_aigc.egg-info}/PKG-INFO +1 -1
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc.egg-info/SOURCES.txt +1 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/LICENSE +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/README.md +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/setup.cfg +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_adaptive_replanning.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_agents.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_assets.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_auto_checkpoint.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_automatic_checkpoints.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_checkpoint_serialization.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_error_handling.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_executor.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_feedback_system.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_integration.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_knowledge_base.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_metaplanner_resume.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_metaplanner_visualization.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_models.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_parallel_execution.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_planner.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_progress_callbacks.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_tools.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_visualization.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/tests/test_workflow_resume.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/__init__.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/agents.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/assets.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/audio.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/character.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/cli.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/comfyui.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/composer_general.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/discovery.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/executor.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/fidelity.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/knowledge.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/llm.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/model_registry.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/models.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/mv_pipeline.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/persistence.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/planner.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/tools_multimodal.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/video.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/visualization.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/vlm_feedback.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/workflow_backend.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/workflow_composer.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/workflow_executor.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/workflow_registry.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/workflow_strategies.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc/workflows.py +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc.egg-info/dependency_links.txt +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc.egg-info/entry_points.txt +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc.egg-info/requires.txt +0 -0
- {vibe_aigc-0.6.1 → vibe_aigc-0.6.3}/vibe_aigc.egg-info/top_level.txt +0 -0
|
@@ -8,7 +8,7 @@ exclude = ["tests*", "docs*", "examples*", "landing*"]
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "vibe-aigc"
|
|
11
|
-
version = "0.6.
|
|
11
|
+
version = "0.6.3"
|
|
12
12
|
description = "A New Paradigm for Content Generation via Agentic Orchestration"
|
|
13
13
|
authors = [{name = "Vibe AIGC Contributors"}]
|
|
14
14
|
license = "MIT"
|
|
@@ -496,8 +496,12 @@ class ToolRegistry:
|
|
|
496
496
|
return "\n".join(lines)
|
|
497
497
|
|
|
498
498
|
|
|
499
|
-
def create_default_registry() -> ToolRegistry:
|
|
500
|
-
"""Create a registry with default tools.
|
|
499
|
+
def create_default_registry(comfyui_url: str = "http://127.0.0.1:8188") -> ToolRegistry:
|
|
500
|
+
"""Create a registry with default tools.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
comfyui_url: URL for ComfyUI server (for image/video generation)
|
|
504
|
+
"""
|
|
501
505
|
registry = ToolRegistry()
|
|
502
506
|
|
|
503
507
|
# Register built-in tools
|
|
@@ -505,4 +509,12 @@ def create_default_registry() -> ToolRegistry:
|
|
|
505
509
|
registry.register(TemplateTool())
|
|
506
510
|
registry.register(CombineTool())
|
|
507
511
|
|
|
512
|
+
# Register ComfyUI-based generation tools (Paper Section 5.4)
|
|
513
|
+
try:
|
|
514
|
+
from .tools_comfyui import create_comfyui_tools
|
|
515
|
+
for tool in create_comfyui_tools(comfyui_url):
|
|
516
|
+
registry.register(tool)
|
|
517
|
+
except ImportError:
|
|
518
|
+
pass # ComfyUI tools optional
|
|
519
|
+
|
|
508
520
|
return registry
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ComfyUI Generation Tools - Image and Video Generation via VibeBackend.
|
|
3
|
+
|
|
4
|
+
Integrates the VibeBackend into the Paper's tool architecture (Section 5.4):
|
|
5
|
+
"The Planner traverses the system's atomic tool library...to select
|
|
6
|
+
the optimal ensemble of components."
|
|
7
|
+
|
|
8
|
+
This makes ComfyUI-based generation available to MetaPlanner as atomic tools.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
from .tools import BaseTool, ToolSpec, ToolResult, ToolCategory
|
|
15
|
+
from .vibe_backend import VibeBackend, GenerationRequest, GenerationResult
|
|
16
|
+
from .discovery import Capability
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ImageGenerationTool(BaseTool):
|
|
20
|
+
"""
|
|
21
|
+
Image generation tool using local ComfyUI.
|
|
22
|
+
|
|
23
|
+
Maps high-level image requests to VibeBackend execution.
|
|
24
|
+
MetaPlanner can use this for visual content generation.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, comfyui_url: str = "http://127.0.0.1:8188"):
|
|
28
|
+
self.comfyui_url = comfyui_url
|
|
29
|
+
self._backend: Optional[VibeBackend] = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def spec(self) -> ToolSpec:
|
|
33
|
+
return ToolSpec(
|
|
34
|
+
name="image_generation",
|
|
35
|
+
description="Generate images using local AI models (FLUX, SD, etc.)",
|
|
36
|
+
category=ToolCategory.IMAGE,
|
|
37
|
+
input_schema={
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["prompt"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"prompt": {"type": "string", "description": "Image description"},
|
|
42
|
+
"negative_prompt": {"type": "string", "description": "What to avoid"},
|
|
43
|
+
"style": {"type": "string", "description": "Visual style hints"},
|
|
44
|
+
"width": {"type": "integer", "default": 768},
|
|
45
|
+
"height": {"type": "integer", "default": 512},
|
|
46
|
+
"steps": {"type": "integer", "default": 20},
|
|
47
|
+
"cfg": {"type": "number", "default": 7.0}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
output_schema={
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"image_url": {"type": "string"},
|
|
54
|
+
"quality_score": {"type": "number"},
|
|
55
|
+
"feedback": {"type": "string"}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
examples=[
|
|
59
|
+
{
|
|
60
|
+
"input": {"prompt": "cyberpunk cityscape at night, neon lights, rain"},
|
|
61
|
+
"output": {"image_url": "http://...", "quality_score": 7.5}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
async def _ensure_backend(self) -> VibeBackend:
|
|
67
|
+
"""Lazily initialize backend."""
|
|
68
|
+
if self._backend is None:
|
|
69
|
+
self._backend = VibeBackend(
|
|
70
|
+
comfyui_url=self.comfyui_url,
|
|
71
|
+
enable_vlm=True,
|
|
72
|
+
max_attempts=2,
|
|
73
|
+
quality_threshold=6.0
|
|
74
|
+
)
|
|
75
|
+
await self._backend.initialize()
|
|
76
|
+
return self._backend
|
|
77
|
+
|
|
78
|
+
async def execute(
|
|
79
|
+
self,
|
|
80
|
+
inputs: Dict[str, Any],
|
|
81
|
+
context: Optional[Dict[str, Any]] = None
|
|
82
|
+
) -> ToolResult:
|
|
83
|
+
"""Generate an image based on inputs."""
|
|
84
|
+
try:
|
|
85
|
+
backend = await self._ensure_backend()
|
|
86
|
+
|
|
87
|
+
# Build prompt from inputs + context (knowledge base hints)
|
|
88
|
+
prompt = inputs.get("prompt", "")
|
|
89
|
+
style = inputs.get("style", "")
|
|
90
|
+
|
|
91
|
+
# If knowledge base provided style hints, incorporate them
|
|
92
|
+
if context and "technical_specs" in context:
|
|
93
|
+
specs = context["technical_specs"]
|
|
94
|
+
if "lighting" in specs:
|
|
95
|
+
prompt += f", {', '.join(specs['lighting'])}"
|
|
96
|
+
if "color_grading" in specs:
|
|
97
|
+
prompt += f", {specs['color_grading']}"
|
|
98
|
+
|
|
99
|
+
if style:
|
|
100
|
+
prompt = f"{prompt}, {style}"
|
|
101
|
+
|
|
102
|
+
result = await backend.generate(GenerationRequest(
|
|
103
|
+
prompt=prompt,
|
|
104
|
+
capability=Capability.TEXT_TO_IMAGE,
|
|
105
|
+
negative_prompt=inputs.get("negative_prompt", ""),
|
|
106
|
+
width=inputs.get("width", 768),
|
|
107
|
+
height=inputs.get("height", 512),
|
|
108
|
+
steps=inputs.get("steps", 20),
|
|
109
|
+
cfg=inputs.get("cfg", 7.0)
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
if result.success:
|
|
113
|
+
return ToolResult(
|
|
114
|
+
success=True,
|
|
115
|
+
output={
|
|
116
|
+
"image_url": result.output_url,
|
|
117
|
+
"quality_score": result.quality_score,
|
|
118
|
+
"feedback": result.feedback,
|
|
119
|
+
"strengths": result.strengths,
|
|
120
|
+
"weaknesses": result.weaknesses
|
|
121
|
+
},
|
|
122
|
+
metadata={
|
|
123
|
+
"model_used": result.model_used,
|
|
124
|
+
"attempts": result.attempts
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
return ToolResult(
|
|
129
|
+
success=False,
|
|
130
|
+
output=None,
|
|
131
|
+
error=result.error
|
|
132
|
+
)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
return ToolResult(
|
|
135
|
+
success=False,
|
|
136
|
+
output=None,
|
|
137
|
+
error=str(e)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class VideoGenerationTool(BaseTool):
|
|
142
|
+
"""
|
|
143
|
+
Video generation tool using local ComfyUI.
|
|
144
|
+
|
|
145
|
+
Uses the I2V pipeline: FLUX → Wan I2V animation.
|
|
146
|
+
MetaPlanner can use this for motion content.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, comfyui_url: str = "http://127.0.0.1:8188"):
|
|
150
|
+
self.comfyui_url = comfyui_url
|
|
151
|
+
self._backend: Optional[VibeBackend] = None
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def spec(self) -> ToolSpec:
|
|
155
|
+
return ToolSpec(
|
|
156
|
+
name="video_generation",
|
|
157
|
+
description="Generate videos using local AI models (FLUX + Wan I2V)",
|
|
158
|
+
category=ToolCategory.VIDEO,
|
|
159
|
+
input_schema={
|
|
160
|
+
"type": "object",
|
|
161
|
+
"required": ["prompt"],
|
|
162
|
+
"properties": {
|
|
163
|
+
"prompt": {"type": "string", "description": "Video description"},
|
|
164
|
+
"negative_prompt": {"type": "string", "description": "What to avoid"},
|
|
165
|
+
"style": {"type": "string", "description": "Visual style hints"},
|
|
166
|
+
"motion": {"type": "string", "description": "Motion description"},
|
|
167
|
+
"width": {"type": "integer", "default": 832},
|
|
168
|
+
"height": {"type": "integer", "default": 480},
|
|
169
|
+
"frames": {"type": "integer", "default": 33},
|
|
170
|
+
"fps": {"type": "integer", "default": 16}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
output_schema={
|
|
174
|
+
"type": "object",
|
|
175
|
+
"properties": {
|
|
176
|
+
"video_url": {"type": "string"},
|
|
177
|
+
"duration_seconds": {"type": "number"}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
examples=[
|
|
181
|
+
{
|
|
182
|
+
"input": {"prompt": "samurai walking through rain", "frames": 33},
|
|
183
|
+
"output": {"video_url": "http://...", "duration_seconds": 2.0}
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
async def _ensure_backend(self) -> VibeBackend:
|
|
189
|
+
"""Lazily initialize backend."""
|
|
190
|
+
if self._backend is None:
|
|
191
|
+
self._backend = VibeBackend(
|
|
192
|
+
comfyui_url=self.comfyui_url,
|
|
193
|
+
enable_vlm=False, # VLM slower for video
|
|
194
|
+
max_attempts=1
|
|
195
|
+
)
|
|
196
|
+
await self._backend.initialize()
|
|
197
|
+
return self._backend
|
|
198
|
+
|
|
199
|
+
async def execute(
|
|
200
|
+
self,
|
|
201
|
+
inputs: Dict[str, Any],
|
|
202
|
+
context: Optional[Dict[str, Any]] = None
|
|
203
|
+
) -> ToolResult:
|
|
204
|
+
"""Generate a video based on inputs."""
|
|
205
|
+
try:
|
|
206
|
+
backend = await self._ensure_backend()
|
|
207
|
+
|
|
208
|
+
# Build prompt
|
|
209
|
+
prompt = inputs.get("prompt", "")
|
|
210
|
+
style = inputs.get("style", "")
|
|
211
|
+
motion = inputs.get("motion", "")
|
|
212
|
+
|
|
213
|
+
# Incorporate knowledge base hints
|
|
214
|
+
if context and "technical_specs" in context:
|
|
215
|
+
specs = context["technical_specs"]
|
|
216
|
+
if "camera" in specs:
|
|
217
|
+
prompt += f", {', '.join(specs['camera'][:2])}"
|
|
218
|
+
if "editing" in specs:
|
|
219
|
+
motion = motion or specs["editing"][0] if specs["editing"] else ""
|
|
220
|
+
|
|
221
|
+
if style:
|
|
222
|
+
prompt = f"{prompt}, {style}"
|
|
223
|
+
if motion:
|
|
224
|
+
prompt = f"{prompt}, {motion}"
|
|
225
|
+
|
|
226
|
+
frames = inputs.get("frames", 33)
|
|
227
|
+
fps = inputs.get("fps", 16)
|
|
228
|
+
|
|
229
|
+
result = await backend.generate(GenerationRequest(
|
|
230
|
+
prompt=prompt,
|
|
231
|
+
capability=Capability.TEXT_TO_VIDEO,
|
|
232
|
+
negative_prompt=inputs.get("negative_prompt", ""),
|
|
233
|
+
width=inputs.get("width", 832),
|
|
234
|
+
height=inputs.get("height", 480),
|
|
235
|
+
frames=frames,
|
|
236
|
+
steps=20,
|
|
237
|
+
cfg=5.0
|
|
238
|
+
))
|
|
239
|
+
|
|
240
|
+
if result.success:
|
|
241
|
+
return ToolResult(
|
|
242
|
+
success=True,
|
|
243
|
+
output={
|
|
244
|
+
"video_url": result.output_url,
|
|
245
|
+
"duration_seconds": frames / fps
|
|
246
|
+
},
|
|
247
|
+
metadata={
|
|
248
|
+
"frames": frames,
|
|
249
|
+
"fps": fps
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
return ToolResult(
|
|
254
|
+
success=False,
|
|
255
|
+
output=None,
|
|
256
|
+
error=result.error
|
|
257
|
+
)
|
|
258
|
+
except Exception as e:
|
|
259
|
+
return ToolResult(
|
|
260
|
+
success=False,
|
|
261
|
+
output=None,
|
|
262
|
+
error=str(e)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def create_comfyui_tools(comfyui_url: str = "http://127.0.0.1:8188") -> list:
|
|
267
|
+
"""Create ComfyUI-based generation tools."""
|
|
268
|
+
return [
|
|
269
|
+
ImageGenerationTool(comfyui_url=comfyui_url),
|
|
270
|
+
VideoGenerationTool(comfyui_url=comfyui_url)
|
|
271
|
+
]
|
|
@@ -150,6 +150,10 @@ class VibeBackend:
|
|
|
150
150
|
import random
|
|
151
151
|
request.seed = random.randint(0, 2**32 - 1)
|
|
152
152
|
|
|
153
|
+
# Special handling for TEXT_TO_VIDEO: use I2V pipeline
|
|
154
|
+
if request.capability == Capability.TEXT_TO_VIDEO:
|
|
155
|
+
return await self._generate_video_via_i2v(request)
|
|
156
|
+
|
|
153
157
|
# Try to get workflow
|
|
154
158
|
workflow = await self._get_workflow(request)
|
|
155
159
|
if not workflow:
|
|
@@ -388,6 +392,199 @@ class VibeBackend:
|
|
|
388
392
|
except Exception as e:
|
|
389
393
|
return GenerationResult(success=False, error=str(e))
|
|
390
394
|
|
|
395
|
+
async def _generate_video_via_i2v(self, request: GenerationRequest) -> GenerationResult:
|
|
396
|
+
"""Generate video via Image-to-Video pipeline.
|
|
397
|
+
|
|
398
|
+
Two-step process:
|
|
399
|
+
1. Generate base image with TEXT_TO_IMAGE
|
|
400
|
+
2. Animate with IMAGE_TO_VIDEO
|
|
401
|
+
"""
|
|
402
|
+
print("\n[1/2] Generating base image...")
|
|
403
|
+
|
|
404
|
+
# Step 1: Generate image
|
|
405
|
+
image_workflow = self._create_flux_image_workflow(
|
|
406
|
+
prompt=request.prompt,
|
|
407
|
+
negative=request.negative_prompt,
|
|
408
|
+
width=request.width,
|
|
409
|
+
height=request.height,
|
|
410
|
+
seed=request.seed
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
image_result = await self._execute_workflow(image_workflow)
|
|
414
|
+
if not image_result.success:
|
|
415
|
+
return GenerationResult(
|
|
416
|
+
success=False,
|
|
417
|
+
error=f"Image generation failed: {image_result.error}"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
print(f" Base image: {image_result.output_path}")
|
|
421
|
+
|
|
422
|
+
# Step 2: Upload image and animate
|
|
423
|
+
print("\n[2/2] Animating with I2V...")
|
|
424
|
+
|
|
425
|
+
# Download image
|
|
426
|
+
async with aiohttp.ClientSession() as session:
|
|
427
|
+
async with session.get(image_result.output_url) as resp:
|
|
428
|
+
image_data = await resp.read()
|
|
429
|
+
|
|
430
|
+
# Upload to ComfyUI
|
|
431
|
+
form = aiohttp.FormData()
|
|
432
|
+
form.add_field('image', image_data, filename='input.png', content_type='image/png')
|
|
433
|
+
|
|
434
|
+
async with session.post(f"{self.url}/upload/image", data=form) as resp:
|
|
435
|
+
upload_result = await resp.json()
|
|
436
|
+
uploaded_name = upload_result.get("name", "input.png")
|
|
437
|
+
print(f" Uploaded: {uploaded_name}")
|
|
438
|
+
|
|
439
|
+
# Create I2V workflow
|
|
440
|
+
i2v_workflow = self._create_wan_i2v_workflow(
|
|
441
|
+
uploaded_image=uploaded_name,
|
|
442
|
+
prompt=request.prompt,
|
|
443
|
+
negative=request.negative_prompt,
|
|
444
|
+
width=request.width,
|
|
445
|
+
height=request.height,
|
|
446
|
+
frames=request.frames,
|
|
447
|
+
seed=request.seed
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
video_result = await self._execute_workflow(i2v_workflow)
|
|
451
|
+
if not video_result.success:
|
|
452
|
+
return GenerationResult(
|
|
453
|
+
success=False,
|
|
454
|
+
error=f"Animation failed: {video_result.error}"
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
print(f" Video: {video_result.output_path}")
|
|
458
|
+
return video_result
|
|
459
|
+
|
|
460
|
+
def _create_flux_image_workflow(
|
|
461
|
+
self, prompt: str, negative: str, width: int, height: int, seed: int
|
|
462
|
+
) -> Dict[str, Any]:
|
|
463
|
+
"""Create FLUX image generation workflow."""
|
|
464
|
+
return {
|
|
465
|
+
"1": {
|
|
466
|
+
"class_type": "CheckpointLoaderSimple",
|
|
467
|
+
"inputs": {"ckpt_name": "flux1-dev-fp8.safetensors"}
|
|
468
|
+
},
|
|
469
|
+
"2": {
|
|
470
|
+
"class_type": "CLIPTextEncode",
|
|
471
|
+
"inputs": {"text": prompt, "clip": ["1", 1]}
|
|
472
|
+
},
|
|
473
|
+
"3": {
|
|
474
|
+
"class_type": "CLIPTextEncode",
|
|
475
|
+
"inputs": {"text": negative or "blurry, distorted, ugly", "clip": ["1", 1]}
|
|
476
|
+
},
|
|
477
|
+
"4": {
|
|
478
|
+
"class_type": "EmptyLatentImage",
|
|
479
|
+
"inputs": {"width": width, "height": height, "batch_size": 1}
|
|
480
|
+
},
|
|
481
|
+
"5": {
|
|
482
|
+
"class_type": "KSampler",
|
|
483
|
+
"inputs": {
|
|
484
|
+
"seed": seed,
|
|
485
|
+
"steps": 20,
|
|
486
|
+
"cfg": 3.5,
|
|
487
|
+
"sampler_name": "euler",
|
|
488
|
+
"scheduler": "simple",
|
|
489
|
+
"denoise": 1.0,
|
|
490
|
+
"model": ["1", 0],
|
|
491
|
+
"positive": ["2", 0],
|
|
492
|
+
"negative": ["3", 0],
|
|
493
|
+
"latent_image": ["4", 0]
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
"6": {
|
|
497
|
+
"class_type": "VAEDecode",
|
|
498
|
+
"inputs": {"samples": ["5", 0], "vae": ["1", 2]}
|
|
499
|
+
},
|
|
500
|
+
"7": {
|
|
501
|
+
"class_type": "SaveImage",
|
|
502
|
+
"inputs": {"images": ["6", 0], "filename_prefix": "vibe_base"}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
def _create_wan_i2v_workflow(
|
|
507
|
+
self, uploaded_image: str, prompt: str, negative: str,
|
|
508
|
+
width: int, height: int, frames: int, seed: int
|
|
509
|
+
) -> Dict[str, Any]:
|
|
510
|
+
"""Create Wan 2.1 I2V workflow."""
|
|
511
|
+
return {
|
|
512
|
+
"1": {
|
|
513
|
+
"class_type": "UNETLoader",
|
|
514
|
+
"inputs": {
|
|
515
|
+
"unet_name": "I2V/Wan2_1-I2V-14B-480p_fp8_e4m3fn_scaled_KJ.safetensors",
|
|
516
|
+
"weight_dtype": "fp8_e4m3fn"
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
"2": {
|
|
520
|
+
"class_type": "CLIPLoader",
|
|
521
|
+
"inputs": {
|
|
522
|
+
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
|
523
|
+
"type": "wan"
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
"3": {
|
|
527
|
+
"class_type": "VAELoader",
|
|
528
|
+
"inputs": {"vae_name": "wan_2.1_vae.safetensors"}
|
|
529
|
+
},
|
|
530
|
+
"4": {
|
|
531
|
+
"class_type": "LoadImage",
|
|
532
|
+
"inputs": {"image": uploaded_image}
|
|
533
|
+
},
|
|
534
|
+
"5": {
|
|
535
|
+
"class_type": "CLIPTextEncode",
|
|
536
|
+
"inputs": {"text": prompt + ", smooth motion, cinematic", "clip": ["2", 0]}
|
|
537
|
+
},
|
|
538
|
+
"6": {
|
|
539
|
+
"class_type": "CLIPTextEncode",
|
|
540
|
+
"inputs": {"text": negative or "static, frozen, blurry, distorted", "clip": ["2", 0]}
|
|
541
|
+
},
|
|
542
|
+
"7": {
|
|
543
|
+
"class_type": "WanImageToVideo",
|
|
544
|
+
"inputs": {
|
|
545
|
+
"positive": ["5", 0],
|
|
546
|
+
"negative": ["6", 0],
|
|
547
|
+
"vae": ["3", 0],
|
|
548
|
+
"width": width,
|
|
549
|
+
"height": height,
|
|
550
|
+
"length": frames,
|
|
551
|
+
"batch_size": 1,
|
|
552
|
+
"start_image": ["4", 0]
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
"8": {
|
|
556
|
+
"class_type": "KSampler",
|
|
557
|
+
"inputs": {
|
|
558
|
+
"seed": seed,
|
|
559
|
+
"steps": 30,
|
|
560
|
+
"cfg": 5.0,
|
|
561
|
+
"sampler_name": "euler",
|
|
562
|
+
"scheduler": "normal",
|
|
563
|
+
"denoise": 1.0,
|
|
564
|
+
"model": ["1", 0],
|
|
565
|
+
"positive": ["7", 0],
|
|
566
|
+
"negative": ["7", 1],
|
|
567
|
+
"latent_image": ["7", 2]
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
"9": {
|
|
571
|
+
"class_type": "VAEDecode",
|
|
572
|
+
"inputs": {"samples": ["8", 0], "vae": ["3", 0]}
|
|
573
|
+
},
|
|
574
|
+
"10": {
|
|
575
|
+
"class_type": "VHS_VideoCombine",
|
|
576
|
+
"inputs": {
|
|
577
|
+
"images": ["9", 0],
|
|
578
|
+
"frame_rate": 16,
|
|
579
|
+
"loop_count": 0,
|
|
580
|
+
"filename_prefix": "vibe_i2v",
|
|
581
|
+
"format": "image/webp",
|
|
582
|
+
"pingpong": False,
|
|
583
|
+
"save_output": True
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
391
588
|
def status(self) -> str:
|
|
392
589
|
"""Get backend status."""
|
|
393
590
|
if not self._initialized:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|