amisima commited on
Commit
a0a9ba0
·
verified ·
1 Parent(s): 3b7abff

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -2
app.py CHANGED
@@ -1644,6 +1644,104 @@ def _add_preset_lora(preset, *current):
1644
 
1645
  load_models()
1646
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1647
  CHIPS = [
1648
  "cinematic lighting, shallow depth of field",
1649
  "slow push in",
@@ -1817,7 +1915,7 @@ CSS = """
1817
  border-radius: 14px !important;
1818
  box-shadow: 0 8px 22px rgba(29, 78, 216, .25);
1819
  }
1820
- #search-btn, #search-put, #extend-btn, #lora-identify, #profile-refresh, #seed-dice, #turbo-btn, #profile-save, #profile-load, #profile-delete {
1821
  min-height: 42px !important; border-radius: 12px !important; font-weight: 600 !important;
1822
  }
1823
 
@@ -1856,7 +1954,7 @@ HERO = """
1856
  <a href="https://huggingface.co/spaces/multimodalart/minimax-h3" target="_blank" rel="noopener">text / image to video</a></p>
1857
  <div class="pills">
1858
  <span>33B</span><span>joint video + audio</span><span>Turbo lora: 4&ndash;8 steps</span>
1859
- <span>ComfyUI lora accepted</span><span>up to 9 references</span><span>5 custom lora slots</span><span>named profiles</span><span>CivitAI search + links</span><span>scene continuation</span><span>kohya + LoKr auto-convert</span><span>GPU cost estimate</span><span>clip stitching with audio</span>
1860
  </div>
1861
  </div>
1862
  """
@@ -1918,6 +2016,33 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, GPU cost, profiles, cl
1918
  value="The character walks through a neon-lit street in the rain, humming to themselves",
1919
  )
1920
  upsample = gr.Checkbox(label="✨ Upsample prompt", value=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1921
  with gr.Accordion("💡 Quick tags — click to add", open=False):
1922
  with gr.Row(elem_classes="chip-row"):
1923
  chip_buttons_a = [gr.Button(text, size="sm", variant="secondary") for text in CHIPS[:4]]
@@ -2254,6 +2379,13 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, GPU cost, profiles, cl
2254
  # A CivitAI download link is a bare number, so the slots can be named from CivitAI's own public
2255
  # model-versions endpoint: the title, the version, the file behind `fileId`, and the trigger words. Pressing
2256
  # Enter in a slot names that set as well, so the button is only there for a paste that never gets an Enter.
 
 
 
 
 
 
 
2257
  lora_identify_btn.click(identify_loras, lora_references, lora_names, api_name=False)
2258
  for _field in lora_references:
2259
  _field.submit(identify_loras, lora_references, lora_names, api_name=False)
 
1644
 
1645
  load_models()
1646
 
1647
+ # ------------------------------------------------------------------------------------------------------------------
1648
+ # Structured prompt builder
1649
+ # ------------------------------------------------------------------------------------------------------------------
1650
+ # H3 was trained on the output of H3-Context-IR, a preprocessor that rewrites a plain request into labelled sections,
1651
+ # and MiniMax's own model card calls that structure "critical to the quality of the final output". Nothing in this
1652
+ # pipeline adds it: the string reaches the transformer as typed. So the builder writes the sections instead - the
1653
+ # shot description, the soundscape and the music, in the order and under the names the model was trained to read.
1654
+ #
1655
+ # Two details from the official guide are worth knowing, because getting them wrong looks like a model fault:
1656
+ # dialogue must be verbatim inside <d> tags or the mouth moves with no words in it, and reference tags have to
1657
+ # appear in the order the inputs were connected.
1658
+
1659
+ IR_SHOT_TYPES = {
1660
+ "(none)": "",
1661
+ "live-action, cinematic": "Live-action, cinematic",
1662
+ "live-action, documentary": "Live-action, documentary, handheld",
1663
+ "studio portrait": "Live-action, studio portrait lighting",
1664
+ "anime": "2D anime, crisp lineart",
1665
+ "3D animation": "3D animation, stylised",
1666
+ }
1667
+
1668
+ IR_CAMERA = {
1669
+ "(none)": "",
1670
+ "static": "The camera holds a static frame",
1671
+ "slow push in": "The camera pushes in with small amplitude at slow speed",
1672
+ "slow pull back": "The camera pulls back with small amplitude at slow speed",
1673
+ "truck right": "The camera trucks right with small amplitude at slow speed",
1674
+ "truck left": "The camera trucks left with small amplitude at slow speed",
1675
+ "orbit": "The camera orbits the subject with medium amplitude at slow speed",
1676
+ "handheld follow": "The camera follows handheld with small amplitude at moderate speed",
1677
+ "tilt up": "The camera tilts up with small amplitude at slow speed",
1678
+ "crane up and back": "The camera cranes up and back with large amplitude at slow speed",
1679
+ }
1680
+
1681
+ IR_SOUNDSCAPE = {
1682
+ "(none)": "",
1683
+ "quiet room": "A quiet room tone with small incidental sounds - fabric, footsteps, a distant door.",
1684
+ "rain and traffic": "Rain ticks against glass over the low hum of distant traffic.",
1685
+ "outdoors, wind": "Wind moves through the scene, carrying faint birdsong and rustling leaves.",
1686
+ "city street": "City ambience: passing cars, footsteps on pavement, indistinct voices further off.",
1687
+ "interior, machinery": "A steady mechanical hum underneath, with occasional metallic ticks.",
1688
+ "crowd": "A crowd murmurs at a middle distance, individual voices indistinct.",
1689
+ }
1690
+
1691
+ IR_MUSIC = {
1692
+ "(none)": "",
1693
+ "no music": "None.",
1694
+ "slow strings": "Sustained cello notes at a slow tempo with widely spaced piano tones.",
1695
+ "warm piano": "A warm solo piano at a slow tempo, sparse and unhurried.",
1696
+ "tense low drone": "A low synth drone with a slow rising tension.",
1697
+ "upbeat electronic": "An upbeat electronic pulse at a moderate tempo.",
1698
+ }
1699
+
1700
+
1701
+ def build_ir_prompt(description, shot_type, camera, soundscape, music, speaker, dialogue,
1702
+ reference_count):
1703
+ """Write the labelled sections H3 was trained on, around what the user typed."""
1704
+ body = (description or "").strip().rstrip(".")
1705
+ if not body:
1706
+ raise gr.Error("Describe the shot first - the builder writes the structure around it.")
1707
+
1708
+ lines = []
1709
+
1710
+ # The reference line comes first, and names the pictures in connection order, which is what the
1711
+ # model expects to match against its inputs.
1712
+ count = int(reference_count or 0)
1713
+ if count > 0:
1714
+ tags = ", ".join(f"<Picture {index}>" for index in range(1, count + 1))
1715
+ lines.append(
1716
+ f"For the target video, at 0.00 seconds into the target video, {tags} "
1717
+ f"(from [Shot 1]) {'is' if count == 1 else 'are'} fully referenced."
1718
+ )
1719
+ lines.append("")
1720
+
1721
+ shot = " ".join(part for part in (IR_SHOT_TYPES.get(shot_type, ""),) if part)
1722
+ described = f"{shot}, {body}." if shot else f"{body}."
1723
+ if count > 0:
1724
+ described += (" The subject keeps the appearance shown in the reference images, and the "
1725
+ "setting keeps its layout.")
1726
+ if IR_CAMERA.get(camera):
1727
+ described += f" {IR_CAMERA[camera]}."
1728
+
1729
+ if (dialogue or "").strip():
1730
+ who = (speaker or "S1").strip() or "S1"
1731
+ spoken = dialogue.strip().strip('"')
1732
+ # Delivery and identity go outside the tag; only the language and the verbatim words go in,
1733
+ # otherwise the mouth moves correctly with nothing in it.
1734
+ described += f" The speaker ({who}) says: <d>[English] {spoken}</d>"
1735
+
1736
+ lines.append(f"integrated_multimodal_description: [Shot 1] {described}")
1737
+ lines.append("")
1738
+ lines.append(f"overall_soundscape: {IR_SOUNDSCAPE.get(soundscape) or 'Ambient sound suited to the scene.'}")
1739
+ lines.append("")
1740
+ lines.append(f"non_diegetic_music: {IR_MUSIC.get(music) or 'None.'}")
1741
+
1742
+ return "\n".join(lines)
1743
+
1744
+
1745
  CHIPS = [
1746
  "cinematic lighting, shallow depth of field",
1747
  "slow push in",
 
1915
  border-radius: 14px !important;
1916
  box-shadow: 0 8px 22px rgba(29, 78, 216, .25);
1917
  }
1918
+ #ir-btn, #search-btn, #search-put, #extend-btn, #lora-identify, #profile-refresh, #seed-dice, #turbo-btn, #profile-save, #profile-load, #profile-delete {
1919
  min-height: 42px !important; border-radius: 12px !important; font-weight: 600 !important;
1920
  }
1921
 
 
1954
  <a href="https://huggingface.co/spaces/multimodalart/minimax-h3" target="_blank" rel="noopener">text / image to video</a></p>
1955
  <div class="pills">
1956
  <span>33B</span><span>joint video + audio</span><span>Turbo lora: 4&ndash;8 steps</span>
1957
+ <span>ComfyUI lora accepted</span><span>up to 9 references</span><span>5 custom lora slots</span><span>structured prompt builder</span><span>named profiles</span><span>CivitAI search + links</span><span>scene continuation</span><span>kohya + LoKr auto-convert</span><span>GPU cost estimate</span><span>clip stitching with audio</span>
1958
  </div>
1959
  </div>
1960
  """
 
2016
  value="The character walks through a neon-lit street in the rain, humming to themselves",
2017
  )
2018
  upsample = gr.Checkbox(label="✨ Upsample prompt", value=False)
2019
+ with gr.Accordion("🎬 Structured prompt builder (what H3 was trained on)", open=False):
2020
+ gr.Markdown(
2021
+ "H3 was trained on the output of a preprocessor that rewrites a request into labelled "
2022
+ "sections, and MiniMax call that structure *critical to the quality of the final output*. "
2023
+ "Nothing here adds it on its own — your text reaches the model as typed. Describe the shot "
2024
+ "in the prompt box above, set the pieces below, and press **Build**.\n\n"
2025
+ "**Dialogue has to be verbatim.** Speech is generated together with the picture, so naming "
2026
+ "that someone speaks without giving the words produces correct mouth shapes with nothing in "
2027
+ "them. Aim for 350–500 words of description for a full scene."
2028
+ )
2029
+ with gr.Row():
2030
+ ir_shot = gr.Dropdown(list(IR_SHOT_TYPES), value="live-action, cinematic",
2031
+ label="Shot type")
2032
+ ir_camera = gr.Dropdown(list(IR_CAMERA), value="slow push in", label="Camera move")
2033
+ with gr.Row():
2034
+ ir_sound = gr.Dropdown(list(IR_SOUNDSCAPE), value="(none)", label="Soundscape")
2035
+ ir_music = gr.Dropdown(list(IR_MUSIC), value="no music", label="Music")
2036
+ with gr.Row():
2037
+ ir_speaker = gr.Textbox(value="S1", label="Speaker id", max_lines=1, scale=1)
2038
+ ir_dialogue = gr.Textbox(label="Dialogue, word for word", scale=4,
2039
+ placeholder="I get off at the next station.")
2040
+ ir_references = gr.Slider(0, MAX_IMAGE_SLOTS, value=1, step=1,
2041
+ label="Reference images to name",
2042
+ info="Named in connection order, as <Picture 1>, <Picture 2> …")
2043
+ ir_button = gr.Button("🎬 Build the structured prompt", variant="secondary",
2044
+ elem_id="ir-btn")
2045
+
2046
  with gr.Accordion("💡 Quick tags — click to add", open=False):
2047
  with gr.Row(elem_classes="chip-row"):
2048
  chip_buttons_a = [gr.Button(text, size="sm", variant="secondary") for text in CHIPS[:4]]
 
2379
  # A CivitAI download link is a bare number, so the slots can be named from CivitAI's own public
2380
  # model-versions endpoint: the title, the version, the file behind `fileId`, and the trigger words. Pressing
2381
  # Enter in a slot names that set as well, so the button is only there for a paste that never gets an Enter.
2382
+ ir_button.click(
2383
+ build_ir_prompt,
2384
+ [prompt, ir_shot, ir_camera, ir_sound, ir_music, ir_speaker, ir_dialogue, ir_references],
2385
+ prompt,
2386
+ api_name=False,
2387
+ )
2388
+
2389
  lora_identify_btn.click(identify_loras, lora_references, lora_names, api_name=False)
2390
  for _field in lora_references:
2391
  _field.submit(identify_loras, lora_references, lora_names, api_name=False)