alisson-anjos commited on
Commit
1bdf731
·
1 Parent(s): cdbc85e

Align face ID app with RoPE reference conditioning

Browse files
app.py CHANGED
@@ -16,7 +16,6 @@ import torch
16
  from huggingface_hub import hf_hub_download, snapshot_download
17
 
18
  from ltx_pipelines.distilled import DistilledPipeline
19
- from ltx_pipelines.utils.args import ImageConditioningInput
20
  from ltx_pipelines.utils.media_io import encode_video
21
  from ltx_pipelines.utils.types import OffloadMode
22
  from ltx_core.loader import LoraPathStrengthAndSDOps, LTXV_LORA_COMFY_RENAMING_MAP
@@ -144,9 +143,6 @@ def preload_models_for_zerogpu() -> None:
144
  print("Models preloaded: weights registered for ZeroGPU streaming.")
145
 
146
 
147
- MODE_KEEP_PHOTO = "Animate my photo"
148
- MODE_NEW_SCENE = "Put my face in a new scene"
149
-
150
  # Aspect ratios offered in "new scene" mode -> exact (width, height), each a
151
  # multiple of 64 (so stage-1 at half resolution stays a multiple of 32 for the
152
  # patchifier), ~0.44-0.59 MP.
@@ -181,7 +177,6 @@ def generate_video(
181
  face_image,
182
  prompt: str,
183
  duration: float,
184
- mode: str,
185
  aspect_ratio: str,
186
  seed: int,
187
  randomize_seed: bool,
@@ -202,15 +197,10 @@ def generate_video(
202
  present tense. Including identity attributes (hair, skin tone, eyes,
203
  facial hair, glasses, face shape) noticeably improves fidelity.
204
  duration (float): Video length in seconds, from 1.0 to 8.0.
205
- mode (str): One of "Animate my photo (start from this image)" — the video
206
- starts from and animates the uploaded photo — or "Put my face in a new
207
- scene (text only)" — the whole scene is generated from the prompt with
208
- the person's face preserved.
209
  seed (int): Random seed, 0 to 2147483647. Ignored when randomize_seed=True.
210
  randomize_seed (bool): When true, a fresh random seed is used each run.
211
- aspect_ratio (str): Output aspect ratio for "new scene" mode — one of
212
- "1:1", "4:3", "3:4", "16:9", "9:16". Ignored in "animate my photo"
213
- mode, where the output matches the uploaded photo's aspect ratio.
214
 
215
  Returns:
216
  tuple[str, int]: Filepath of the generated .mp4 video, and the seed used.
@@ -220,7 +210,6 @@ def generate_video(
220
  if face_image is None:
221
  raise ValueError("Please provide a reference face image.")
222
 
223
- use_image_conditioning = mode != MODE_NEW_SCENE
224
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
225
 
226
  frame_rate = 24.0
@@ -238,31 +227,16 @@ def generate_video(
238
  else:
239
  temp_image_path = Path(face_image)
240
 
241
- # Output dimensions:
242
- # - "animate my photo": match the uploaded photo's aspect ratio.
243
- # - "new scene": use the selected aspect ratio (default 4:3).
244
- if use_image_conditioning:
245
- from PIL import Image as _PILImage
246
-
247
- _iw, _ih = (
248
- face_image.size if hasattr(face_image, "size") else _PILImage.open(temp_image_path).size
249
- )
250
- width, height = _dims_for_ratio(_iw / _ih)
251
- else:
252
- width, height = ASPECT_DIMS.get(aspect_ratio, ASPECT_DIMS[DEFAULT_ASPECT])
253
 
254
- # First-frame (I2V) conditioning only in "animate my photo" mode. Identity
255
- # itself is carried by the overlap reference (reference_image_path) in BOTH
256
- # modes — that's the BFS source-phase mechanism the LoRA was trained with.
257
- images = (
258
- [ImageConditioningInput(path=str(temp_image_path), frame_idx=0, strength=1.0)]
259
- if use_image_conditioning
260
- else []
261
- )
262
 
263
  # `ref_t2v:` is the LoRA's trained caption trigger.
264
  identity_prompt = prompt if prompt.strip().startswith("ref_t2v:") else f"ref_t2v: {prompt}"
265
- print(f"Generating (mode={mode!r}, {int(width)}x{int(height)}, {num_frames}f): {identity_prompt}")
266
 
267
  # --- ZeroGPU diagnostics (safe counters only; mem_get_info() asserts on MIG) ---
268
  try:
@@ -302,7 +276,8 @@ def generate_video(
302
  frame_rate=frame_rate,
303
  images=images,
304
  tiling_config=tiling_config,
305
- enhance_prompt=False, # multimodal enhancer disabled (device bug)
 
306
  reference_image_path=str(temp_image_path),
307
  reference_source_id=2.0,
308
  reference_phase_scale=1.0,
@@ -342,12 +317,6 @@ with gr.Blocks(title="LTX-2.3 Face ID 🎥🧑") as demo:
342
  with gr.Column():
343
  face_image = gr.Image(label="Reference Face Image", type="pil")
344
 
345
- mode = gr.Radio(
346
- label="Generation mode",
347
- choices=[MODE_KEEP_PHOTO, MODE_NEW_SCENE],
348
- value=MODE_KEEP_PHOTO,
349
- )
350
-
351
  prompt = gr.Textbox(
352
  label="Prompt",
353
  value=DEFAULT_PROMPT,
@@ -364,7 +333,6 @@ with gr.Blocks(title="LTX-2.3 Face ID 🎥🧑") as demo:
364
  label="Aspect ratio",
365
  choices=list(ASPECT_DIMS.keys()),
366
  value=DEFAULT_ASPECT,
367
- info="For 'Put my face in a new scene'. In 'Animate my photo' the output matches your photo.",
368
  )
369
  seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, value=DEFAULT_SEED, step=1)
370
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
@@ -374,7 +342,7 @@ with gr.Blocks(title="LTX-2.3 Face ID 🎥🧑") as demo:
374
 
375
  generate_btn.click(
376
  fn=generate_video,
377
- inputs=[face_image, prompt, duration, mode, aspect_ratio, seed, randomize_seed],
378
  outputs=[output_video, seed],
379
  )
380
 
@@ -382,25 +350,31 @@ with gr.Blocks(title="LTX-2.3 Face ID 🎥🧑") as demo:
382
  examples=[
383
  [
384
  str(current_dir / "face_ex1.jpg"),
385
- "A young man with dark curly hair and a short beard sits in a sunlit cafe, sipping coffee and smiling warmly at the camera. Soft window light, shallow depth of field, cinematic quality, realistic skin texture.",
386
  3.0,
387
- MODE_NEW_SCENE,
 
 
388
  ],
389
  [
390
  str(current_dir / "face_ex2.jpg"),
391
- "A young woman with long dark hair and glasses walks through a vibrant open-air market, glancing at the camera with a gentle smile as stalls of colorful fabrics pass behind her. Natural daylight, shallow depth of field, cinematic.",
392
  3.0,
393
- MODE_NEW_SCENE,
 
 
394
  ],
395
  [
396
  str(current_dir / "face_ex3.jpg"),
397
- "A young woman with shoulder-length wavy black hair stands on a rooftop at golden hour, the city skyline behind her, turning to look at the camera with a calm expression. Warm cinematic lighting.",
398
  3.0,
399
- MODE_NEW_SCENE,
 
 
400
  ],
401
  ],
402
  fn=generate_video,
403
- inputs=[face_image, prompt, duration, mode],
404
  outputs=[output_video, seed],
405
  label="Example",
406
  cache_examples=False,
 
16
  from huggingface_hub import hf_hub_download, snapshot_download
17
 
18
  from ltx_pipelines.distilled import DistilledPipeline
 
19
  from ltx_pipelines.utils.media_io import encode_video
20
  from ltx_pipelines.utils.types import OffloadMode
21
  from ltx_core.loader import LoraPathStrengthAndSDOps, LTXV_LORA_COMFY_RENAMING_MAP
 
143
  print("Models preloaded: weights registered for ZeroGPU streaming.")
144
 
145
 
 
 
 
146
  # Aspect ratios offered in "new scene" mode -> exact (width, height), each a
147
  # multiple of 64 (so stage-1 at half resolution stays a multiple of 32 for the
148
  # patchifier), ~0.44-0.59 MP.
 
177
  face_image,
178
  prompt: str,
179
  duration: float,
 
180
  aspect_ratio: str,
181
  seed: int,
182
  randomize_seed: bool,
 
197
  present tense. Including identity attributes (hair, skin tone, eyes,
198
  facial hair, glasses, face shape) noticeably improves fidelity.
199
  duration (float): Video length in seconds, from 1.0 to 8.0.
 
 
 
 
200
  seed (int): Random seed, 0 to 2147483647. Ignored when randomize_seed=True.
201
  randomize_seed (bool): When true, a fresh random seed is used each run.
202
+ aspect_ratio (str): Output aspect ratio — one of "1:1", "4:3", "3:4",
203
+ "16:9", "9:16".
 
204
 
205
  Returns:
206
  tuple[str, int]: Filepath of the generated .mp4 video, and the seed used.
 
210
  if face_image is None:
211
  raise ValueError("Please provide a reference face image.")
212
 
 
213
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
214
 
215
  frame_rate = 24.0
 
227
  else:
228
  temp_image_path = Path(face_image)
229
 
230
+ width, height = ASPECT_DIMS.get(aspect_ratio, ASPECT_DIMS[DEFAULT_ASPECT])
 
 
 
 
 
 
 
 
 
 
 
231
 
232
+ # Best-Face-ID was not trained as I2V. The face image must not replace
233
+ # frame 0; it is injected below as source-phase-tagged overlap tokens in
234
+ # a separate RoPE source segment.
235
+ images = []
 
 
 
 
236
 
237
  # `ref_t2v:` is the LoRA's trained caption trigger.
238
  identity_prompt = prompt if prompt.strip().startswith("ref_t2v:") else f"ref_t2v: {prompt}"
239
+ print(f"Generating (reference-overlap, {int(width)}x{int(height)}, {num_frames}f): {identity_prompt}")
240
 
241
  # --- ZeroGPU diagnostics (safe counters only; mem_get_info() asserts on MIG) ---
242
  try:
 
276
  frame_rate=frame_rate,
277
  images=images,
278
  tiling_config=tiling_config,
279
+ enhance_prompt=True,
280
+ enhance_prompt_image_path=str(temp_image_path),
281
  reference_image_path=str(temp_image_path),
282
  reference_source_id=2.0,
283
  reference_phase_scale=1.0,
 
317
  with gr.Column():
318
  face_image = gr.Image(label="Reference Face Image", type="pil")
319
 
 
 
 
 
 
 
320
  prompt = gr.Textbox(
321
  label="Prompt",
322
  value=DEFAULT_PROMPT,
 
333
  label="Aspect ratio",
334
  choices=list(ASPECT_DIMS.keys()),
335
  value=DEFAULT_ASPECT,
 
336
  )
337
  seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, value=DEFAULT_SEED, step=1)
338
  randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
 
342
 
343
  generate_btn.click(
344
  fn=generate_video,
345
+ inputs=[face_image, prompt, duration, aspect_ratio, seed, randomize_seed],
346
  outputs=[output_video, seed],
347
  )
348
 
 
350
  examples=[
351
  [
352
  str(current_dir / "face_ex1.jpg"),
353
+ "A man sits in a sunlit cafe, sipping coffee and smiling warmly at the camera. Soft window light, shallow depth of field, cinematic quality, realistic skin texture.",
354
  3.0,
355
+ DEFAULT_ASPECT,
356
+ DEFAULT_SEED,
357
+ True,
358
  ],
359
  [
360
  str(current_dir / "face_ex2.jpg"),
361
+ "A woman walks through a vibrant open-air market, glancing at the camera with a gentle smile as stalls of colorful fabrics pass behind her. Natural daylight, shallow depth of field, cinematic.",
362
  3.0,
363
+ DEFAULT_ASPECT,
364
+ DEFAULT_SEED,
365
+ True,
366
  ],
367
  [
368
  str(current_dir / "face_ex3.jpg"),
369
+ "A woman stands on a rooftop at golden hour, the city skyline behind her, turning to look at the camera with a calm expression. Warm cinematic lighting.",
370
  3.0,
371
+ DEFAULT_ASPECT,
372
+ DEFAULT_SEED,
373
+ True,
374
  ],
375
  ],
376
  fn=generate_video,
377
+ inputs=[face_image, prompt, duration, aspect_ratio, seed, randomize_seed],
378
  outputs=[output_video, seed],
379
  label="Example",
380
  cache_examples=False,
packages/ltx-pipelines/src/ltx_pipelines/distilled.py CHANGED
@@ -39,6 +39,158 @@ from ltx_pipelines.utils.media_io import encode_video, load_image_and_preprocess
39
  from ltx_pipelines.utils.types import ModalitySpec, OffloadMode
40
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  class DistilledPipeline:
43
  """
44
  Two-stage distilled video generation pipeline.
@@ -142,6 +294,7 @@ class DistilledPipeline:
142
  images: list[ImageConditioningInput],
143
  tiling_config: TilingConfig | None = None,
144
  enhance_prompt: bool = False,
 
145
  stage_1_sigmas: torch.Tensor = DISTILLED_SIGMAS,
146
  stage_2_sigmas: torch.Tensor = STAGE_2_DISTILLED_SIGMAS,
147
  reference_image_path: str | None = None,
@@ -157,7 +310,9 @@ class DistilledPipeline:
157
  (ctx_p,) = self.prompt_encoder(
158
  [prompt],
159
  enhance_first_prompt=enhance_prompt,
160
- enhance_prompt_image=images[0][0] if len(images) > 0 else None,
 
 
161
  )
162
  video_context, audio_context = ctx_p.video_encoding, ctx_p.audio_encoding
163
 
 
39
  from ltx_pipelines.utils.types import ModalitySpec, OffloadMode
40
 
41
 
42
+ IDENTITY_PROMPT_ENHANCER_SYSTEM_PROMPT = """You will receive two inputs:
43
+
44
+ 1. A reference face image showing the person whose appearance must be preserved.
45
+ 2. An original `ref_t2v` video caption describing the scene, action, environment, camera framing, movement, lighting, mood, clothing, and props.
46
+
47
+ Your task is to analyze the reference face image, extract the person's visible appearance characteristics, and merge those characteristics naturally into the original video caption.
48
+
49
+ STRICT EDITING RULE:
50
+
51
+ Treat the original video caption as locked text.
52
+
53
+ You are allowed to make only these edits:
54
+
55
+ 1. Expand the first subject phrase:
56
+ - Replace only `A person`, `A man`, or `A woman` with the same original subject noun plus visible appearance details.
57
+ - The original subject noun must remain present.
58
+ - Insert the appearance details immediately around the original subject noun.
59
+ - Do not delete or replace any words that appear after the original subject phrase.
60
+
61
+ 2. Optionally replace later subject pronouns:
62
+ - `They`, `He`, or `She` may be replaced with a short appearance-based reference.
63
+ - Preserve every verb, object, action, and remaining word from the original sentence.
64
+ - Do not alter the order of the words or actions.
65
+
66
+ Everything else in the original caption is immutable.
67
+
68
+ You must preserve verbatim:
69
+
70
+ - clothing and costumes;
71
+ - actions;
72
+ - action order;
73
+ - environments;
74
+ - objects and props;
75
+ - camera framing;
76
+ - camera movement;
77
+ - lighting;
78
+ - mood;
79
+ - adjectives;
80
+ - temporal transitions;
81
+ - all principal nouns and verbs.
82
+
83
+ CRITICAL PRESERVATION RULES:
84
+
85
+ - Never remove clothing or costume descriptions.
86
+ - Never remove phrases between the original subject and the main action.
87
+ - Never replace original words with synonyms.
88
+ - Never summarize or simplify the caption.
89
+ - Never improve or creatively rewrite the caption.
90
+ - Never add new actions, clothing, props, people, or environments.
91
+ - Never mention or infer the person's identity or name.
92
+ - Never repeat labels such as `REFERENCE FACE IMAGE` or `ORIGINAL VIDEO CAPTION`.
93
+ - Never write introductory text such as:
94
+ - `Okay`
95
+ - `I understand`
96
+ - `Let's proceed`
97
+ - `Here is the merged caption`
98
+ - `The result is`
99
+
100
+ FACE IMAGE ANALYSIS:
101
+
102
+ Extract only clearly visible, non-sensitive appearance details, such as:
103
+
104
+ - apparent age group;
105
+ - skin tone;
106
+ - hair color;
107
+ - hair length;
108
+ - hair texture;
109
+ - hairstyle;
110
+ - face shape;
111
+ - visible facial hair;
112
+ - glasses;
113
+ - clearly visible eye color;
114
+ - other distinctive visible facial features.
115
+
116
+ Do not:
117
+
118
+ - identify the person;
119
+ - repeat a name supplied in image metadata or surrounding text;
120
+ - infer ethnicity, nationality, occupation, personality, religion, health, or background;
121
+ - invent unclear characteristics;
122
+ - infer clothing from the face image.
123
+
124
+ MINIMUM-EDIT PROCEDURE:
125
+
126
+ Step 1:
127
+ Copy the original caption exactly.
128
+
129
+ Step 2:
130
+ Locate only the first subject phrase:
131
+ - `A person`
132
+ - `A man`
133
+ - `A woman`
134
+
135
+ Step 3:
136
+ Expand that phrase with the extracted appearance while preserving the original subject noun.
137
+
138
+ Example:
139
+
140
+ Original:
141
+ `A person dressed in a pirate costume walking along a harbor promenade`
142
+
143
+ Allowed:
144
+ `A fair-skinned adult person with short, neatly combed blond hair dressed in a pirate costume walking along a harbor promenade`
145
+
146
+ Forbidden:
147
+ `A fair-skinned adult man with short blond hair walking along a harbor promenade`
148
+
149
+ The forbidden version removes:
150
+ `dressed in a pirate costume`
151
+
152
+ Step 4:
153
+ For later pronouns, either preserve the original pronoun or replace only the pronoun with a concise appearance-based subject reference.
154
+
155
+ Example:
156
+
157
+ Original:
158
+ `They adjust the hat, hold a prop telescope, and continue strolling near the water.`
159
+
160
+ Allowed:
161
+ `The short-haired person adjusts the hat, holds a prop telescope, and continues strolling near the water.`
162
+
163
+ Do not modify:
164
+ `adjusts the hat, holds a prop telescope, and continues strolling near the water.`
165
+
166
+ FINAL VALIDATION:
167
+
168
+ Before answering, silently compare the output with the original caption and verify:
169
+
170
+ - every original costume and clothing phrase remains;
171
+ - every original action remains;
172
+ - every original prop remains;
173
+ - every original environment remains;
174
+ - every original framing term remains;
175
+ - every original lighting and mood phrase remains;
176
+ - the action order is unchanged;
177
+ - no original principal word was removed;
178
+ - no synonym replaced an original word;
179
+ - only subject appearance information was added;
180
+ - no introductory or explanatory text appears.
181
+
182
+ OUTPUT REQUIREMENT:
183
+
184
+ Output exactly one line containing only the final `ref_t2v` caption.
185
+
186
+ Do not output analysis, headings, labels, explanations, acknowledgements, or quotation marks.
187
+
188
+ The line must begin with exactly:
189
+
190
+ ref_t2v:
191
+ """
192
+
193
+
194
  class DistilledPipeline:
195
  """
196
  Two-stage distilled video generation pipeline.
 
294
  images: list[ImageConditioningInput],
295
  tiling_config: TilingConfig | None = None,
296
  enhance_prompt: bool = False,
297
+ enhance_prompt_image_path: str | None = None,
298
  stage_1_sigmas: torch.Tensor = DISTILLED_SIGMAS,
299
  stage_2_sigmas: torch.Tensor = STAGE_2_DISTILLED_SIGMAS,
300
  reference_image_path: str | None = None,
 
310
  (ctx_p,) = self.prompt_encoder(
311
  [prompt],
312
  enhance_first_prompt=enhance_prompt,
313
+ enhance_prompt_image=enhance_prompt_image_path,
314
+ enhance_prompt_seed=seed,
315
+ enhance_prompt_system_prompt=IDENTITY_PROMPT_ENHANCER_SYSTEM_PROMPT if enhance_prompt_image_path else None,
316
  )
317
  video_context, audio_context = ctx_p.video_encoding, ctx_p.audio_encoding
318
 
packages/ltx-pipelines/src/ltx_pipelines/utils/blocks.py CHANGED
@@ -563,6 +563,7 @@ class PromptEncoder:
563
  enhance_first_prompt: bool = False,
564
  enhance_prompt_image: str | None = None,
565
  enhance_prompt_seed: int = 42,
 
566
  ) -> list[EmbeddingsProcessorOutput]:
567
  """Encode *prompts* through Gemma -> embeddings processor, freeing each model after use."""
568
  logger.info("Building text encoder from %s", self._gemma_root)
@@ -570,7 +571,11 @@ class PromptEncoder:
570
  if enhance_first_prompt:
571
  prompts = list(prompts)
572
  prompts[0] = generate_enhanced_prompt(
573
- text_encoder, prompts[0], enhance_prompt_image, seed=enhance_prompt_seed
 
 
 
 
574
  )
575
  raw_outputs = text_encoder.encode(prompts)
576
  logger.info("Text encoder done, building embeddings processor from %s", self._checkpoint_path)
 
563
  enhance_first_prompt: bool = False,
564
  enhance_prompt_image: str | None = None,
565
  enhance_prompt_seed: int = 42,
566
+ enhance_prompt_system_prompt: str | None = None,
567
  ) -> list[EmbeddingsProcessorOutput]:
568
  """Encode *prompts* through Gemma -> embeddings processor, freeing each model after use."""
569
  logger.info("Building text encoder from %s", self._gemma_root)
 
571
  if enhance_first_prompt:
572
  prompts = list(prompts)
573
  prompts[0] = generate_enhanced_prompt(
574
+ text_encoder,
575
+ prompts[0],
576
+ enhance_prompt_image,
577
+ seed=enhance_prompt_seed,
578
+ system_prompt=enhance_prompt_system_prompt,
579
  )
580
  raw_outputs = text_encoder.encode(prompts)
581
  logger.info("Text encoder done, building embeddings processor from %s", self._checkpoint_path)
packages/ltx-pipelines/src/ltx_pipelines/utils/denoisers.py CHANGED
@@ -51,6 +51,8 @@ def _repeat_state(state: LatentState, n: int) -> LatentState:
51
  positions=_repeat(state.positions),
52
  clean_latent=_repeat(state.clean_latent),
53
  attention_mask=_repeat(state.attention_mask) if state.attention_mask is not None else None,
 
 
54
  )
55
 
56
 
 
51
  positions=_repeat(state.positions),
52
  clean_latent=_repeat(state.clean_latent),
53
  attention_mask=_repeat(state.attention_mask) if state.attention_mask is not None else None,
54
+ ref_overlap_len=state.ref_overlap_len,
55
+ ref_seg_value=state.ref_seg_value,
56
  )
57
 
58
 
packages/ltx-pipelines/src/ltx_pipelines/utils/helpers.py CHANGED
@@ -315,6 +315,7 @@ def generate_enhanced_prompt(
315
  image_path: str | None = None,
316
  image_long_side: int = 896,
317
  seed: int = 42,
 
318
  ) -> str:
319
  """Generate an enhanced prompt from a text encoder and a prompt."""
320
  image = None
@@ -322,9 +323,9 @@ def generate_enhanced_prompt(
322
  image = decode_image(image_path=image_path)
323
  image = torch.tensor(image)
324
  image = resize_aspect_ratio_preserving(image, image_long_side).to(torch.uint8)
325
- prompt = text_encoder.enhance_i2v(prompt, image, seed=seed)
326
  else:
327
- prompt = text_encoder.enhance_t2v(prompt, seed=seed)
328
  logging.info(f"Enhanced prompt: {prompt}")
329
  return clean_response(prompt)
330
 
 
315
  image_path: str | None = None,
316
  image_long_side: int = 896,
317
  seed: int = 42,
318
+ system_prompt: str | None = None,
319
  ) -> str:
320
  """Generate an enhanced prompt from a text encoder and a prompt."""
321
  image = None
 
323
  image = decode_image(image_path=image_path)
324
  image = torch.tensor(image)
325
  image = resize_aspect_ratio_preserving(image, image_long_side).to(torch.uint8)
326
+ prompt = text_encoder.enhance_i2v(prompt, image, seed=seed, system_prompt=system_prompt)
327
  else:
328
+ prompt = text_encoder.enhance_t2v(prompt, seed=seed, system_prompt=system_prompt)
329
  logging.info(f"Enhanced prompt: {prompt}")
330
  return clean_response(prompt)
331