mpasila commited on
Commit
35d08d7
·
verified ·
1 Parent(s): 49c5b9b

feat(app): add auto-canvas selection and update conditioner space

Browse files

- Update `CONDITIONER_SPACE` default to `mpasila/qwen3vl-conditioner`.
- Add 3:2 and 2:3 aspect ratio options to `CANVASES`.
- Implement `auto_canvas` logic to automatically select the closest canvas aspect ratio based on uploaded image or video dimensions.
- Bind `auto_canvas` to image and video component change events in the Gradio UI.

Files changed (1) hide show
  1. app.py +48 -1
app.py CHANGED
@@ -20,7 +20,7 @@ import spaces
20
  import gradio as gr
21
 
22
  MODEL_REPO = os.environ.get("H3_MODEL_REPO", "MiniMaxAI/MiniMax-H3")
23
- CONDITIONER_SPACE = os.environ.get("H3_CONDITIONER", "multimodalart/qwen3vl-conditioner")
24
  # `lazy` moves all 72.16 GiB onto the card on the first GPU call and leaves it there; `offload` hands placement to
25
  # `ComponentsManager.enable_auto_cpu_offload`. Startup placement is not an option here — see `load_models`.
26
  PLACEMENT = os.environ.get("H3_PLACEMENT", "lazy").lower()
@@ -54,6 +54,11 @@ CANVASES = {
54
  "1024x768 · 4:3 full": (768, 1024),
55
  "576x768 · 3:4 fast": (768, 576),
56
  "768x1024 · 3:4 full": (1024, 768),
 
 
 
 
 
57
  # 21:9
58
  "1152x512 · 21:9 fast": (512, 1152),
59
  "1536x672 · 21:9 full": (672, 1536),
@@ -498,6 +503,43 @@ def probe(path: str) -> tuple[float | None, float | None]:
498
  return video, audio
499
 
500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  def collect(image_paths, audio_path, video_path) -> list[tuple[str, str]]:
502
  """The `(kind, path)` references of a request, **in the order the model reads them**.
503
 
@@ -947,6 +989,11 @@ with gr.Blocks(title="MiniMax-H3 Reference Custom Lora") as demo:
947
  duration_controls, [audio, video, match], [match, duration], show_progress="hidden", api_name=False
948
  )
949
 
 
 
 
 
 
950
  # `reference, strength, reference, strength, ...`, which is how `generate` unpacks them.
951
  lora_inputs = [field for pair in zip(lora_references, lora_scales) for field in pair]
952
  lora_upload.upload(_fill_lora_slots, [lora_upload, *lora_references], lora_references, api_name=False)
 
20
  import gradio as gr
21
 
22
  MODEL_REPO = os.environ.get("H3_MODEL_REPO", "MiniMaxAI/MiniMax-H3")
23
+ CONDITIONER_SPACE = os.environ.get("H3_CONDITIONER", "mpasila/qwen3vl-conditioner")
24
  # `lazy` moves all 72.16 GiB onto the card on the first GPU call and leaves it there; `offload` hands placement to
25
  # `ComponentsManager.enable_auto_cpu_offload`. Startup placement is not an option here — see `load_models`.
26
  PLACEMENT = os.environ.get("H3_PLACEMENT", "lazy").lower()
 
54
  "1024x768 · 4:3 full": (768, 1024),
55
  "576x768 · 3:4 fast": (768, 576),
56
  "768x1024 · 3:4 full": (1024, 768),
57
+ # 3:2 / 2:3
58
+ "864x576 · 3:2 fast": (576, 864),
59
+ "1152x768 · 3:2 full": (768, 1152),
60
+ "576x864 · 2:3 fast": (864, 576),
61
+ "768x1152 · 2:3 full": (1152, 768),
62
  # 21:9
63
  "1152x512 · 21:9 fast": (512, 1152),
64
  "1536x672 · 21:9 full": (672, 1536),
 
503
  return video, audio
504
 
505
 
506
+ def _media_dimensions(path: str) -> tuple[int, int]:
507
+ """`(width, height)` of an image or a video file, from its first stream."""
508
+ from PIL import Image
509
+
510
+ try:
511
+ with Image.open(path) as image:
512
+ return image.size
513
+ except Exception:
514
+ pass
515
+ import av
516
+
517
+ with av.open(path) as container:
518
+ stream = container.streams.video[0]
519
+ return stream.width, stream.height
520
+
521
+
522
+ def closest_canvas(path: str | None) -> str | None:
523
+ """The canvas label whose aspect ratio is closest to a media file's, or `None` when the file is
524
+ missing or unreadable."""
525
+ if not path:
526
+ return None
527
+ try:
528
+ width, height = _media_dimensions(path)
529
+ except Exception:
530
+ return None
531
+ if not width or not height:
532
+ return None
533
+ target = width / height
534
+ return min(CANVASES, key=lambda label: abs(CANVASES[label][1] / CANVASES[label][0] - target))
535
+
536
+
537
+ def auto_canvas(path):
538
+ """Set the canvas to the closest aspect ratio of an uploaded image or video."""
539
+ label = closest_canvas(path)
540
+ return gr.update(value=label) if label else gr.update()
541
+
542
+
543
  def collect(image_paths, audio_path, video_path) -> list[tuple[str, str]]:
544
  """The `(kind, path)` references of a request, **in the order the model reads them**.
545
 
 
989
  duration_controls, [audio, video, match], [match, duration], show_progress="hidden", api_name=False
990
  )
991
 
992
+ # Auto-select the canvas whose aspect ratio is closest to an uploaded image or video.
993
+ for image in images:
994
+ image.change(auto_canvas, image, canvas, show_progress="hidden", api_name=False)
995
+ video.change(auto_canvas, video, canvas, show_progress="hidden", api_name=False)
996
+
997
  # `reference, strength, reference, strength, ...`, which is how `generate` unpacks them.
998
  lora_inputs = [field for pair in zip(lora_references, lora_scales) for field in pair]
999
  lora_upload.upload(_fill_lora_slots, [lora_upload, *lora_references], lora_references, api_name=False)