JonathanColetti commited on
Commit
3aa8bab
·
verified ·
1 Parent(s): b4efd73

cross-theme warning, crossfade, and a swap time bound to the clip

Browse files
Files changed (1) hide show
  1. app.py +53 -7
app.py CHANGED
@@ -214,6 +214,11 @@ WORLDS = {f"world {w['idx']} · {w['prompt'][:70]}": w["idx"] for w in INFO["wor
214
  WORLD_LABELS = list(WORLDS)
215
  PROMPT_LABELS = list(PROMPTS)
216
  W = {i: lbl for lbl, i in WORLDS.items()} # by world id, for the default
 
 
 
 
 
217
  print(f"[boot] ready: step {INFO['step']}, {len(PROMPTS)} prompts, "
218
  f"{len(WORLDS)} worlds", flush=True)
219
 
@@ -245,7 +250,8 @@ def write_mp4(frames):
245
  return path
246
 
247
 
248
- def _duration(world, steer_to, steer_text, steer_at, seconds, seed, *args, **kwargs):
 
249
  # Measured: ~12 s to open a world (load, prime the cache, decode its 81 frames)
250
  # and ~1.02 s per block, a block being 0.75 s of video. Declared tight on purpose:
251
  # ZeroGPU compares the request against the visitor's remaining quota, not the
@@ -255,7 +261,7 @@ def _duration(world, steer_to, steer_text, steer_at, seconds, seed, *args, **kwa
255
 
256
  @spaces.GPU(duration=_duration)
257
  def run(world: str, steer_to: str, steer_text: str, steer_at: float,
258
- seconds: float, seed: int):
259
  """Stream video from a cached world, optionally swapping the prompt mid-stream.
260
 
261
  Args:
@@ -263,6 +269,7 @@ def run(world: str, steer_to: str, steer_text: str, steer_at: float,
263
  steer_to: a prompt from the 96-prompt bank to swap to, or the no-steer option.
264
  steer_text: free text to swap to instead; takes precedence over steer_to.
265
  steer_at: seconds into the clip at which to swap the conditioning.
 
266
  seconds: how much video to generate, at 16 fps.
267
  seed: RNG seed for the block sampler.
268
 
@@ -270,7 +277,10 @@ def run(world: str, steer_to: str, steer_text: str, steer_at: float,
270
  (preview frame, HUD line, finished mp4) — the mp4 only on the last yield.
271
  """
272
  total_frames = int(float(seconds) * FPS)
273
- steer_frame = int(float(steer_at) * FPS)
 
 
 
274
 
275
  text = (steer_text or "").strip()
276
  if text:
@@ -319,7 +329,7 @@ def run(world: str, steer_to: str, steer_text: str, steer_at: float,
319
  cv2.IMREAD_COLOR)[:, :, ::-1])
320
 
321
  if pending_steer and len(frames) >= steer_frame:
322
- engine.steer(**steer_with)
323
  pending_steer = False
324
 
325
  now = time.perf_counter()
@@ -357,6 +367,31 @@ Running on shared ZeroGPU hardware, so about 0.7x real time here; a dedicated GP
357
  does about 2.7x. [Code]({GITHUB}) · [Weights](https://huggingface.co/{LIVEWAN_REPO})
358
  """
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  with gr.Blocks(title="LiveWan") as demo:
361
  with gr.Column(elem_id="col-container"):
362
  gr.Markdown(INTRO)
@@ -371,10 +406,15 @@ with gr.Blocks(title="LiveWan") as demo:
371
  label="Or steer to your own text", lines=2,
372
  placeholder="A lighthouse in a storm",
373
  info="Encoded with umt5-xxl. Overrides the selection above.")
374
- steer_at = gr.Slider(1, 25, value=6, step=0.5,
375
- label="Swap at (seconds in)")
376
  seconds = gr.Slider(5, 30, value=15, step=1,
377
  label="Generate (seconds of video)")
 
 
 
 
 
 
378
  seed = gr.Number(value=0, precision=0, label="Seed")
379
  run_btn = gr.Button("Stream", variant="primary", size="lg")
380
 
@@ -386,7 +426,13 @@ with gr.Blocks(title="LiveWan") as demo:
386
  video = gr.Video(label="The clip, at 16 fps", autoplay=True,
387
  height=368)
388
 
389
- run_btn.click(run, inputs=[world, steer_to, steer_text, steer_at, seconds, seed],
 
 
 
 
 
 
390
  outputs=[preview, stats, video], concurrency_limit=1)
391
 
392
  demo.queue(max_size=12).launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)
 
214
  WORLD_LABELS = list(WORLDS)
215
  PROMPT_LABELS = list(PROMPTS)
216
  W = {i: lbl for lbl, i in WORLDS.items()} # by world id, for the default
217
+ # Themes are what the local UI uses to warn about a steer that will smear. The four
218
+ # shipped worlds are named by their own bank prompt, so the world id is a prompt id.
219
+ from wanstreamer.serve.conditioning import theme_of # noqa: E402
220
+ PROMPT_THEME = {p["idx"]: p["theme"] for p in INFO["prompts"]}
221
+ WORLD_THEME = {w["idx"]: theme_of(w["idx"]) for w in INFO["worlds"]}
222
  print(f"[boot] ready: step {INFO['step']}, {len(PROMPTS)} prompts, "
223
  f"{len(WORLDS)} worlds", flush=True)
224
 
 
250
  return path
251
 
252
 
253
+ def _duration(world, steer_to, steer_text, steer_at, crossfade, seconds, seed,
254
+ *args, **kwargs):
255
  # Measured: ~12 s to open a world (load, prime the cache, decode its 81 frames)
256
  # and ~1.02 s per block, a block being 0.75 s of video. Declared tight on purpose:
257
  # ZeroGPU compares the request against the visitor's remaining quota, not the
 
261
 
262
  @spaces.GPU(duration=_duration)
263
  def run(world: str, steer_to: str, steer_text: str, steer_at: float,
264
+ crossfade: int, seconds: float, seed: int):
265
  """Stream video from a cached world, optionally swapping the prompt mid-stream.
266
 
267
  Args:
 
269
  steer_to: a prompt from the 96-prompt bank to swap to, or the no-steer option.
270
  steer_text: free text to swap to instead; takes precedence over steer_to.
271
  steer_at: seconds into the clip at which to swap the conditioning.
272
+ crossfade: blocks to interpolate the conditioning over; 0 swaps instantly.
273
  seconds: how much video to generate, at 16 fps.
274
  seed: RNG seed for the block sampler.
275
 
 
277
  (preview frame, HUD line, finished mp4) — the mp4 only on the last yield.
278
  """
279
  total_frames = int(float(seconds) * FPS)
280
+ # A swap at or past the end of the clip would never fire and the run would look
281
+ # like the control did nothing. Keep it at least two seconds from the end so the
282
+ # result of the swap is actually visible.
283
+ steer_frame = int(min(float(steer_at), max(1.0, float(seconds) - 2.0)) * FPS)
284
 
285
  text = (steer_text or "").strip()
286
  if text:
 
329
  cv2.IMREAD_COLOR)[:, :, ::-1])
330
 
331
  if pending_steer and len(frames) >= steer_frame:
332
+ engine.steer(crossfade=int(crossfade), **steer_with)
333
  pending_steer = False
334
 
335
  now = time.perf_counter()
 
367
  does about 2.7x. [Code]({GITHUB}) · [Weights](https://huggingface.co/{LIVEWAN_REPO})
368
  """
369
 
370
+ def steer_warning(world, steer_to, steer_text):
371
+ """The same warning the local UI shows before a steer that will smear.
372
+
373
+ Cross-theme steers ask the model to reconcile a K/V cache full of one kind of
374
+ scene with a prompt describing another, and it smears rather than resolving.
375
+ Free text gets no theme, so it cannot be checked.
376
+ """
377
+ if (steer_text or "").strip() or not steer_to or steer_to == NO_STEER:
378
+ return gr.update(visible=False)
379
+ wt = WORLD_THEME.get(WORLDS[world])
380
+ pt = PROMPT_THEME.get(PROMPTS[steer_to])
381
+ if not wt or not pt or wt == pt:
382
+ return gr.update(visible=False)
383
+ return gr.update(visible=True, value=(
384
+ f"⚠️ {wt.lower()} to {pt.lower()} is a jump across themes, so expect the "
385
+ f"picture to smear rather than resolve. Raise the crossfade to soften it, "
386
+ f"or steer somewhere closer to where you started."))
387
+
388
+
389
+ def bound_swap(seconds, steer_at):
390
+ """A swap must land far enough before the end to be worth watching."""
391
+ hi = max(1.0, float(seconds) - 2.0)
392
+ return gr.update(maximum=hi, value=min(float(steer_at), hi))
393
+
394
+
395
  with gr.Blocks(title="LiveWan") as demo:
396
  with gr.Column(elem_id="col-container"):
397
  gr.Markdown(INTRO)
 
406
  label="Or steer to your own text", lines=2,
407
  placeholder="A lighthouse in a storm",
408
  info="Encoded with umt5-xxl. Overrides the selection above.")
409
+ warning = gr.Markdown(visible=False)
 
410
  seconds = gr.Slider(5, 30, value=15, step=1,
411
  label="Generate (seconds of video)")
412
+ steer_at = gr.Slider(1, 13, value=6, step=0.5,
413
+ label="Swap at (seconds in)")
414
+ crossfade = gr.Slider(0, 12, value=0, step=1,
415
+ label="Crossfade (blocks)",
416
+ info="0 swaps instantly. Blending over a few "
417
+ "blocks softens a big jump.")
418
  seed = gr.Number(value=0, precision=0, label="Seed")
419
  run_btn = gr.Button("Stream", variant="primary", size="lg")
420
 
 
426
  video = gr.Video(label="The clip, at 16 fps", autoplay=True,
427
  height=368)
428
 
429
+ for control in (world, steer_to, steer_text):
430
+ control.change(steer_warning, [world, steer_to, steer_text], warning)
431
+ seconds.change(bound_swap, [seconds, steer_at], steer_at)
432
+
433
+ run_btn.click(run,
434
+ inputs=[world, steer_to, steer_text, steer_at, crossfade,
435
+ seconds, seed],
436
  outputs=[preview, stats, video], concurrency_limit=1)
437
 
438
  demo.queue(max_size=12).launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)