Spaces:
Running on Zero
Running on Zero
Upload app.py
Browse files
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–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–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)
|