Skip to content

Commit 5955ddf

Browse files
authored
[Partner Nodes] feat(Luma): add support for Luma Rays 3.2 (Comfy-Org#14540)
Signed-off-by: bigcat88 <bigcat88@icloud.com>
1 parent 5ef0092 commit 5955ddf

2 files changed

Lines changed: 652 additions & 54 deletions

File tree

comfy_api_nodes/apis/luma.py

Lines changed: 98 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
class LumaIO:
1111
LUMA_REF = "LUMA_REF"
1212
LUMA_CONCEPTS = "LUMA_CONCEPTS"
13+
LUMA_RAY32_KEYFRAME = "LUMA_RAY32_KEYFRAME"
1314

1415

1516
class LumaReference:
@@ -20,13 +21,14 @@ def __init__(self, image: torch.Tensor, weight: float):
2021
def create_api_model(self, download_url: str):
2122
return LumaImageRef(url=download_url, weight=self.weight)
2223

24+
2325
class LumaReferenceChain:
24-
def __init__(self, first_ref: LumaReference=None):
26+
def __init__(self, first_ref: LumaReference = None):
2527
self.refs: list[LumaReference] = []
2628
if first_ref:
2729
self.refs.append(first_ref)
2830

29-
def add(self, luma_ref: LumaReference=None):
31+
def add(self, luma_ref: LumaReference = None):
3032
self.refs.append(luma_ref)
3133

3234
def create_api_model(self, download_urls: list[str], max_refs=4):
@@ -124,7 +126,7 @@ def get_luma_concepts(include_none=False):
124126
"pull_out",
125127
"aerial",
126128
"crane_up",
127-
"eye_level"
129+
"eye_level",
128130
]
129131

130132

@@ -162,8 +164,8 @@ class LumaVideoModelOutputDuration(str, Enum):
162164

163165

164166
class LumaGenerationType(str, Enum):
165-
video = 'video'
166-
image = 'image'
167+
video = "video"
168+
image = "image"
167169

168170

169171
class LumaState(str, Enum):
@@ -174,86 +176,109 @@ class LumaState(str, Enum):
174176

175177

176178
class LumaAssets(BaseModel):
177-
video: Optional[str] = Field(None, description='The URL of the video')
178-
image: Optional[str] = Field(None, description='The URL of the image')
179-
progress_video: Optional[str] = Field(None, description='The URL of the progress video')
179+
video: Optional[str] = Field(None, description="The URL of the video")
180+
image: Optional[str] = Field(None, description="The URL of the image")
181+
progress_video: Optional[str] = Field(None, description="The URL of the progress video")
180182

181183

182184
class LumaImageRef(BaseModel):
183185
"""Used for image gen"""
184-
url: str = Field(..., description='The URL of the image reference')
185-
weight: confloat(ge=0.0, le=1.0) = Field(..., description='The weight of the image reference')
186+
187+
url: str = Field(..., description="The URL of the image reference")
188+
weight: confloat(ge=0.0, le=1.0) = Field(..., description="The weight of the image reference")
186189

187190

188191
class LumaImageReference(BaseModel):
189192
"""Used for video gen"""
190-
type: Optional[str] = Field('image', description='Input type, defaults to image')
191-
url: str = Field(..., description='The URL of the image')
193+
194+
type: Optional[str] = Field("image", description="Input type, defaults to image")
195+
url: str = Field(..., description="The URL of the image")
192196

193197

194198
class LumaModifyImageRef(BaseModel):
195-
url: str = Field(..., description='The URL of the image reference')
196-
weight: confloat(ge=0.0, le=1.0) = Field(..., description='The weight of the image reference')
199+
url: str = Field(..., description="The URL of the image reference")
200+
weight: confloat(ge=0.0, le=1.0) = Field(..., description="The weight of the image reference")
197201

198202

199203
class LumaCharacterRef(BaseModel):
200-
identity0: LumaImageIdentity = Field(..., description='The image identity object')
204+
identity0: LumaImageIdentity = Field(..., description="The image identity object")
201205

202206

203207
class LumaImageIdentity(BaseModel):
204-
images: list[str] = Field(..., description='The URLs of the image identity')
208+
images: list[str] = Field(..., description="The URLs of the image identity")
205209

206210

207211
class LumaGenerationReference(BaseModel):
208-
type: str = Field('generation', description='Input type, defaults to generation')
209-
id: str = Field(..., description='The ID of the generation')
212+
type: str = Field("generation", description="Input type, defaults to generation")
213+
id: str = Field(..., description="The ID of the generation")
210214

211215

212216
class LumaKeyframes(BaseModel):
213-
frame0: Optional[Union[LumaImageReference, LumaGenerationReference]] = Field(None, description='')
214-
frame1: Optional[Union[LumaImageReference, LumaGenerationReference]] = Field(None, description='')
217+
frame0: Optional[Union[LumaImageReference, LumaGenerationReference]] = Field(None, description="")
218+
frame1: Optional[Union[LumaImageReference, LumaGenerationReference]] = Field(None, description="")
215219

216220

217221
class LumaConceptObject(BaseModel):
218-
key: str = Field(..., description='Camera Concept name')
222+
key: str = Field(..., description="Camera Concept name")
219223

220224

221225
class LumaImageGenerationRequest(BaseModel):
222-
prompt: str = Field(..., description='The prompt of the generation')
223-
model: LumaImageModel = Field(LumaImageModel.photon_1, description='The image model used for the generation')
224-
aspect_ratio: Optional[LumaAspectRatio] = Field(LumaAspectRatio.ratio_16_9, description='The aspect ratio of the generation')
225-
image_ref: Optional[list[LumaImageRef]] = Field(None, description='List of image reference objects')
226-
style_ref: Optional[list[LumaImageRef]] = Field(None, description='List of style reference objects')
227-
character_ref: Optional[LumaCharacterRef] = Field(None, description='The image identity object')
228-
modify_image_ref: Optional[LumaModifyImageRef] = Field(None, description='The modify image reference object')
226+
prompt: str = Field(..., description="The prompt of the generation")
227+
model: LumaImageModel = Field(LumaImageModel.photon_1, description="The image model used for the generation")
228+
aspect_ratio: Optional[LumaAspectRatio] = Field(LumaAspectRatio.ratio_16_9)
229+
image_ref: Optional[list[LumaImageRef]] = Field(None, description="List of image reference objects")
230+
style_ref: Optional[list[LumaImageRef]] = Field(None, description="List of style reference objects")
231+
character_ref: Optional[LumaCharacterRef] = Field(None, description="The image identity object")
232+
modify_image_ref: Optional[LumaModifyImageRef] = Field(None, description="The modify image reference object")
229233

230234

231235
class LumaGenerationRequest(BaseModel):
232-
prompt: str = Field(..., description='The prompt of the generation')
233-
model: LumaVideoModel = Field(LumaVideoModel.ray_2, description='The video model used for the generation')
234-
duration: Optional[LumaVideoModelOutputDuration] = Field(None, description='The duration of the generation')
235-
aspect_ratio: Optional[LumaAspectRatio] = Field(None, description='The aspect ratio of the generation')
236-
resolution: Optional[LumaVideoOutputResolution] = Field(None, description='The resolution of the generation')
237-
loop: Optional[bool] = Field(None, description='Whether to loop the video')
238-
keyframes: Optional[LumaKeyframes] = Field(None, description='The keyframes of the generation')
239-
concepts: Optional[list[LumaConceptObject]] = Field(None, description='Camera Concepts to apply to generation')
236+
prompt: str = Field(..., description="The prompt of the generation")
237+
model: LumaVideoModel = Field(LumaVideoModel.ray_2, description="The video model used for the generation")
238+
duration: Optional[LumaVideoModelOutputDuration] = Field(None, description="The duration of the generation")
239+
aspect_ratio: Optional[LumaAspectRatio] = Field(None, description="The aspect ratio of the generation")
240+
resolution: Optional[LumaVideoOutputResolution] = Field(None, description="The resolution of the generation")
241+
loop: Optional[bool] = Field(None, description="Whether to loop the video")
242+
keyframes: Optional[LumaKeyframes] = Field(None, description="The keyframes of the generation")
243+
concepts: Optional[list[LumaConceptObject]] = Field(None, description="Camera Concepts to apply to generation")
240244

241245

242246
class LumaGeneration(BaseModel):
243-
id: str = Field(..., description='The ID of the generation')
244-
generation_type: LumaGenerationType = Field(..., description='Generation type, image or video')
245-
state: LumaState = Field(..., description='The state of the generation')
246-
failure_reason: Optional[str] = Field(None, description='The reason for the state of the generation')
247-
created_at: str = Field(..., description='The date and time when the generation was created')
248-
assets: Optional[LumaAssets] = Field(None, description='The assets of the generation')
249-
model: str = Field(..., description='The model used for the generation')
250-
request: Union[LumaGenerationRequest, LumaImageGenerationRequest] = Field(..., description="The request used for the generation")
247+
id: str = Field(..., description="The ID of the generation")
248+
generation_type: LumaGenerationType = Field(..., description="Generation type, image or video")
249+
state: LumaState = Field(..., description="The state of the generation")
250+
failure_reason: Optional[str] = Field(None, description="The reason for the state of the generation")
251+
created_at: str = Field(..., description="The date and time when the generation was created")
252+
assets: Optional[LumaAssets] = Field(None, description="The assets of the generation")
253+
model: str = Field(..., description="The model used for the generation")
254+
request: Union[LumaGenerationRequest, LumaImageGenerationRequest] = Field(...)
251255

252256

253257
class Luma2ImageRef(BaseModel):
254258
url: str | None = None
255259
data: str | None = None
256260
media_type: str | None = None
261+
generation_id: str | None = Field(None, description="reference a prior generation (extend / source reuse)")
262+
263+
264+
class Luma2VideoEdit(BaseModel):
265+
"""Edit controls for Ray 3.2 ``video_edit`` generations."""
266+
267+
auto_controls: bool | None = Field(None, description="derive a conditioning schedule from the source (recommended)")
268+
strength: str | None = Field(None, description="'adhere_1' .. 'reimagine_3'; constrained by IO.Combo")
269+
270+
271+
class Luma2VideoOptions(BaseModel):
272+
"""Ray 3.2 ``video`` output settings (text / image / keyframe / edit / extend)."""
273+
274+
resolution: str | None = Field(None, description="360p | 540p | 720p | 1080p")
275+
duration: str | None = Field(None, description="5s | 10s")
276+
loop: bool | None = Field(None)
277+
start_frame: Luma2ImageRef | None = Field(None)
278+
end_frame: Luma2ImageRef | None = Field(None)
279+
keyframes: list[Luma2ImageRef] | None = Field(None)
280+
keyframe_indexes: list[int] | None = Field(None)
281+
edit: Luma2VideoEdit | None = Field(None)
257282

258283

259284
class Luma2GenerationRequest(BaseModel):
@@ -266,6 +291,7 @@ class Luma2GenerationRequest(BaseModel):
266291
web_search: bool | None = None
267292
image_ref: list[Luma2ImageRef] | None = None
268293
source: Luma2ImageRef | None = None
294+
video: Luma2VideoOptions | None = Field(None)
269295

270296

271297
class Luma2Generation(BaseModel):
@@ -277,3 +303,31 @@ class Luma2Generation(BaseModel):
277303
output: list[LumaImageReference] | None = None
278304
failure_reason: str | None = None
279305
failure_code: str | None = None
306+
307+
308+
# --- Ray 3.2 multi-keyframe chain ---
309+
310+
LUMA_KEYFRAME_MODE_FRACTION = "fraction" # value in [0.0, 1.0] of the output video duration
311+
LUMA_KEYFRAME_MODE_SECONDS = "seconds" # absolute time, in seconds, from the start of the output
312+
313+
314+
class LumaRay32KeyframeItem:
315+
"""One guide image anchored at a position on the Ray 3.2 output timeline."""
316+
317+
def __init__(self, image: torch.Tensor, mode: str, value: float):
318+
self.image = image
319+
self.mode = mode # LUMA_KEYFRAME_MODE_FRACTION | LUMA_KEYFRAME_MODE_SECONDS
320+
self.value = value
321+
322+
323+
class LumaRay32KeyframeChain:
324+
def __init__(self):
325+
self.items: list[LumaRay32KeyframeItem] = []
326+
327+
def add(self, item: LumaRay32KeyframeItem) -> None:
328+
self.items.append(item)
329+
330+
def clone(self) -> "LumaRay32KeyframeChain":
331+
c = LumaRay32KeyframeChain()
332+
c.items = list(self.items)
333+
return c

0 commit comments

Comments
 (0)