daKhosa commited on
Commit
1efc965
·
1 Parent(s): 83e482c

Use Sulphur LoRAs and strengthen prompt handling

Browse files
Files changed (1) hide show
  1. app.py +43 -4
app.py CHANGED
@@ -28,6 +28,10 @@ OUTPUT_DIR = PERSISTENT / "sulphur_outputs"
28
  MAX_SEED = np.iinfo(np.int32).max
29
  DEFAULT_FRAME_RATE = 24.0
30
  DEFAULT_PROMPT = "Make this image come alive with cinematic motion, smooth animation."
 
 
 
 
31
 
32
  RESOLUTIONS = {
33
  "high": {"16:9": (1536, 1024), "9:16": (1024, 1536), "1:1": (1024, 1024)},
@@ -185,6 +189,37 @@ def _download(repo_id, filename, local_dir):
185
  return path
186
 
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  def _ensure_gemma_model_alias(gemma_weight):
189
  gemma_weight = Path(gemma_weight)
190
  alias = gemma_weight.with_name("model.safetensors")
@@ -205,8 +240,8 @@ def download_assets():
205
  ASSETS_DIR / "loras",
206
  )
207
  distilled_lora = _download(
208
- "DeepBeepMeep/LTX-2",
209
- "ltx-2.3-22b-distilled-lora-384.safetensors",
210
  ASSETS_DIR / "loras",
211
  )
212
  upsampler = _download(
@@ -256,6 +291,8 @@ LORAS = [
256
  LoraPathStrengthAndSDOps(str(ASSETS["distilled_lora"]), 1.0, LTXV_LORA_COMFY_RENAMING_MAP),
257
  LoraPathStrengthAndSDOps(str(ASSETS["sulphur_lora"]), 1.0, LTXV_LORA_COMFY_RENAMING_MAP),
258
  ]
 
 
259
 
260
  pipeline = DistilledPipeline(
261
  distilled_checkpoint_path=str(ASSETS["checkpoint"]),
@@ -319,8 +356,10 @@ def generate_video(
319
  num_frames = ((num_frames - 1 + 7) // 8) * 8 + 1
320
  height = int(height)
321
  width = int(width)
 
322
 
323
  print(f"[generate] {width}x{height}, frames={num_frames}, seed={current_seed}")
 
324
 
325
  images = []
326
  if input_image is not None:
@@ -334,7 +373,7 @@ def generate_video(
334
  log_memory("before pipeline")
335
 
336
  video, audio = pipeline(
337
- prompt=prompt,
338
  seed=current_seed,
339
  height=height,
340
  width=width,
@@ -372,7 +411,7 @@ with gr.Blocks(title="Sulphur LTX Image to Video") as demo:
372
  with gr.Row():
373
  duration = gr.Slider(1.0, 5.0, value=3.0, step=0.1, label="Duration")
374
  with gr.Column():
375
- enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=False)
376
  high_res = gr.Checkbox(label="High Resolution", value=False)
377
 
378
  generate_btn = gr.Button("Generate", variant="primary")
 
28
  MAX_SEED = np.iinfo(np.int32).max
29
  DEFAULT_FRAME_RATE = 24.0
30
  DEFAULT_PROMPT = "Make this image come alive with cinematic motion, smooth animation."
31
+ PROMPT_ADHERENCE_SUFFIX = (
32
+ "Follow the prompt literally. Keep the named subjects, actions, setting, style, "
33
+ "camera motion, and timing consistent with the user's request."
34
+ )
35
 
36
  RESOLUTIONS = {
37
  "high": {"16:9": (1536, 1024), "9:16": (1024, 1536), "1:1": (1024, 1024)},
 
189
  return path
190
 
191
 
192
+ def _safetensors_header(path):
193
+ with open(path, "rb") as reader:
194
+ header_len = struct.unpack("<Q", reader.read(8))[0]
195
+ return json.loads(reader.read(header_len).decode("utf-8"))
196
+
197
+
198
+ def _validate_lora(name, lora):
199
+ path = Path(lora.path)
200
+ if not path.exists():
201
+ raise FileNotFoundError(f"{name} LoRA missing: {path}")
202
+ keys = [key for key in _safetensors_header(path) if key != "__metadata__"]
203
+ mapped = [lora.sd_ops.apply_to_key(key) for key in keys]
204
+ mapped = [key for key in mapped if key is not None]
205
+ lora_a = sum(1 for key in mapped if key.endswith(".lora_A.weight"))
206
+ lora_b = sum(1 for key in mapped if key.endswith(".lora_B.weight"))
207
+ alpha = sum(1 for key in mapped if key.endswith(".alpha"))
208
+ if lora_a == 0 or lora_b == 0:
209
+ raise RuntimeError(f"{name} LoRA has no usable lora_A/lora_B keys after mapping: {path}")
210
+ print(
211
+ f"[lora] active: {name} strength={lora.strength} path={path} "
212
+ f"lora_A={lora_a} lora_B={lora_b} alpha={alpha} size={path.stat().st_size / 1024**3:.2f}GB"
213
+ )
214
+
215
+
216
+ def _prompt_for_model(prompt):
217
+ prompt = " ".join(str(prompt or DEFAULT_PROMPT).split())
218
+ if PROMPT_ADHERENCE_SUFFIX.lower() in prompt.lower():
219
+ return prompt
220
+ return f"{prompt}\n\n{PROMPT_ADHERENCE_SUFFIX}"
221
+
222
+
223
  def _ensure_gemma_model_alias(gemma_weight):
224
  gemma_weight = Path(gemma_weight)
225
  alias = gemma_weight.with_name("model.safetensors")
 
240
  ASSETS_DIR / "loras",
241
  )
242
  distilled_lora = _download(
243
+ "SulphurAI/Sulphur-2-base",
244
+ "distill_loras/ltx-2.3-22b-distilled-lora-1.1_fro90_ceil72_condsafe.safetensors",
245
  ASSETS_DIR / "loras",
246
  )
247
  upsampler = _download(
 
291
  LoraPathStrengthAndSDOps(str(ASSETS["distilled_lora"]), 1.0, LTXV_LORA_COMFY_RENAMING_MAP),
292
  LoraPathStrengthAndSDOps(str(ASSETS["sulphur_lora"]), 1.0, LTXV_LORA_COMFY_RENAMING_MAP),
293
  ]
294
+ _validate_lora("sulphur_distilled_condsafe", LORAS[0])
295
+ _validate_lora("sulphur_experimental_lora_v1", LORAS[1])
296
 
297
  pipeline = DistilledPipeline(
298
  distilled_checkpoint_path=str(ASSETS["checkpoint"]),
 
356
  num_frames = ((num_frames - 1 + 7) // 8) * 8 + 1
357
  height = int(height)
358
  width = int(width)
359
+ model_prompt = _prompt_for_model(prompt)
360
 
361
  print(f"[generate] {width}x{height}, frames={num_frames}, seed={current_seed}")
362
+ print(f"[prompt] {model_prompt}")
363
 
364
  images = []
365
  if input_image is not None:
 
373
  log_memory("before pipeline")
374
 
375
  video, audio = pipeline(
376
+ prompt=model_prompt,
377
  seed=current_seed,
378
  height=height,
379
  width=width,
 
411
  with gr.Row():
412
  duration = gr.Slider(1.0, 5.0, value=3.0, step=0.1, label="Duration")
413
  with gr.Column():
414
+ enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=True)
415
  high_res = gr.Checkbox(label="High Resolution", value=False)
416
 
417
  generate_btn = gr.Button("Generate", variant="primary")