CoolFace
Modelpublic

Spatial9/GravityLLM

sourceHugging Faceapache-2.0updated 7mo agoView on Hugging Face
0likes
1from __future__ import annotations2 3import base644import json5import os6import tempfile7from pathlib import Path8from typing import Any, Dict9 10import gradio as gr11from huggingface_hub import InferenceClient12from huggingface_hub.errors import HfHubHTTPError, InferenceTimeoutError13 14from utils.scene_tools import (15    SCHEMA,16    extract_first_json_block,17    heuristic_scene,18    parse_json_text,19    plot_scene,20    scene_markdown,21    scene_table,22    validate_scene,23)24 25ROOT = Path(__file__).resolve().parent26ASSETS = ROOT / "assets"27EXAMPLES = ROOT / "examples"28 29APP_TITLE = "GravityLLM"30DEFAULT_MODEL_ID = os.getenv("GRAVITYLLM_MODEL_ID", "your-namespace/GravityLLM-AutoPosition")31DEFAULT_BACKEND = os.getenv("GRAVITYLLM_BACKEND", "hybrid")32HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACEHUB_API_TOKEN")33 34SYSTEM_PREFIX = (35    "You are GravityLLM (Spatial9 AutoPosition SLM). "36    "Generate ONLY valid JSON matching the Spatial9Scene schema. "37    "No markdown. No explanation. No code fences.\n\n"38)39 40EXAMPLE_FILES = {41    "Club Drop": EXAMPLES / "club_drop.json",42    "Cinematic Break": EXAMPLES / "cinematic_break.json",43    "Podcast Voice": EXAMPLES / "podcast_voice.json",44}45 46 47def logo_data_uri() -> str:48    logo_bytes = (ASSETS / "spatial9_logo.png").read_bytes()49    return "data:image/png;base64," + base64.b64encode(logo_bytes).decode("utf-8")50 51 52def load_example(name: str) -> str:53    path = EXAMPLE_FILES.get(name, next(iter(EXAMPLE_FILES.values())))54    return path.read_text(encoding="utf-8")55 56 57def build_prompt(payload: Dict[str, Any]) -> str:58    return SYSTEM_PREFIX + "INPUT:\n" + json.dumps(payload, ensure_ascii=False, indent=2) + "\nOUTPUT:\n"59 60 61def remote_generate(62    payload: Dict[str, Any],63    model_id: str,64    temperature: float,65    top_p: float,66    max_new_tokens: int,67    use_grammar: bool,68) -> tuple[Dict[str, Any], str]:69    prompt = build_prompt(payload)70    client = InferenceClient(model=model_id, token=HF_TOKEN)71 72    call_kwargs = dict(73        prompt=prompt,74        model=model_id,75        max_new_tokens=max_new_tokens,76        temperature=temperature,77        top_p=top_p,78        repetition_penalty=1.05,79        return_full_text=False,80    )81    if use_grammar:82        call_kwargs["grammar"] = {"type": "json", "value": SCHEMA}83 84    try:85        response = client.text_generation(**call_kwargs)86        scene = parse_json_text(response)87        return scene, f"remote-model ({model_id})"88    except Exception as first_error:89        if use_grammar:90            try:91                call_kwargs.pop("grammar", None)92                response = client.text_generation(**call_kwargs)93                scene = parse_json_text(response)94                return scene, f"remote-model ({model_id}, grammar-fallback)"95            except Exception as second_error:96                raise RuntimeError(f"{type(first_error).__name__}: {first_error}\n\nFallback: {type(second_error).__name__}: {second_error}") from second_error97        raise98 99 100def write_download_file(scene: Dict[str, Any]) -> str:101    fd, path = tempfile.mkstemp(prefix="gravityllm_scene_", suffix=".json")102    os.close(fd)103    Path(path).write_text(json.dumps(scene, ensure_ascii=False, indent=2), encoding="utf-8")104    return path105 106 107def generate_scene(108    payload_text: str,109    model_id: str,110    backend: str,111    temperature: float,112    top_p: float,113    max_new_tokens: int,114    use_grammar: bool,115):116    try:117        payload = parse_json_text(payload_text)118    except Exception as exc:119        msg = f"### Invalid input JSON\n\n- {type(exc).__name__}: {exc}"120        return "", msg, None, [], None, "Fix the input JSON and try again."121 122    backend = backend or DEFAULT_BACKEND123    model_id = model_id.strip() or DEFAULT_MODEL_ID124 125    scene = None126    backend_used = "rules-engine demo"127    status = "Scene generated."128 129    if backend in {"remote-model", "hybrid"}:130        try:131            scene, backend_used = remote_generate(payload, model_id, temperature, top_p, max_new_tokens, use_grammar)132            status = f"Generated from remote model: {model_id}"133        except (InferenceTimeoutError, HfHubHTTPError, RuntimeError, ValueError, json.JSONDecodeError) as exc:134            if backend == "remote-model":135                msg = f"### Remote generation failed\n\n- {type(exc).__name__}: {exc}"136                return "", msg, None, [], None, "Remote inference failed."137            scene = heuristic_scene(payload)138            backend_used = "rules-engine demo (remote fallback)"139            status = f"Remote generation failed; heuristic fallback used. Details: {type(exc).__name__}: {exc}"140 141    if scene is None:142        scene = heuristic_scene(payload)143 144    valid, errors = validate_scene(scene)145    download_path = write_download_file(scene)146    figure = plot_scene(scene)147    table = scene_table(scene)148    summary = scene_markdown(scene, valid, errors, backend_used)149    return json.dumps(scene, ensure_ascii=False, indent=2), summary, figure, table, download_path, status150 151 152def validate_only(scene_text: str):153    try:154        scene = parse_json_text(scene_text)155    except Exception as exc:156        return f"### Invalid scene JSON\n\n- {type(exc).__name__}: {exc}", None, []157    valid, errors = validate_scene(scene)158    summary = scene_markdown(scene, valid, errors, "manual validation")159    return summary, plot_scene(scene), scene_table(scene)160 161 162def build_payload(target_format, style, section, bpm, energy, max_objects):163    payload = {164        "target_format": target_format,165        "max_objects": int(max_objects),166        "style": style,167        "section": section,168        "global": {"bpm": int(bpm), "energy": float(energy)},169        "stems": [170            {"id": "lead", "class": "lead_vocal", "lufs": -17.0, "transient": 0.25, "band_energy": {"low": 0.08, "mid": 0.67, "high": 0.25}, "leadness": 0.96},171            {"id": "kick", "class": "kick", "lufs": -10.6, "transient": 0.96, "band_energy": {"low": 0.82, "mid": 0.12, "high": 0.06}, "leadness": 0.22},172            {"id": "bass", "class": "bass", "lufs": -12.5, "transient": 0.58, "band_energy": {"low": 0.86, "mid": 0.10, "high": 0.04}, "leadness": 0.30},173            {"id": "pad", "class": "pad", "lufs": -21.5, "transient": 0.05, "band_energy": {"low": 0.20, "mid": 0.50, "high": 0.30}, "leadness": 0.08},174            {"id": "fx", "class": "fx", "lufs": -24.0, "transient": 0.22, "band_energy": {"low": 0.10, "mid": 0.24, "high": 0.66}, "leadness": 0.04},175        ],176        "rules": [177            {"type": "anchor", "track_class": "lead_vocal", "az_deg": 0, "el_deg": 10, "dist_m": 1.6},178            {"type": "mono_low_end", "hz_below": 120},179            {"type": "width_pref", "track_class": "pad", "min_width": 0.75},180        ],181    }182    return json.dumps(payload, ensure_ascii=False, indent=2)183 184 185hero_html = f"""186<div class="hero-wrap">187  <div class="hero-left">188    <div class="hero-logo-card">189      <img class="hero-logo" src="{logo_data_uri()}" alt="Spatial9 logo"/>190    </div>191  </div>192  <div class="hero-right">193    <div class="eyebrow">SPATIAL9 • HUGGING FACE SPACE</div>194    <h1>GravityLLM Studio</h1>195    <p class="hero-copy">196      Constraint-conditioned immersive scene generation with schema-guided JSON output,197      remote Hugging Face inference, heuristic fallback, and a live spatial preview.198    </p>199    <div class="hero-chips">200      <span>IAMF Ready</span>201      <span>Schema Validated</span>202      <span>Spatial Preview</span>203      <span>Branded Demo</span>204    </div>205  </div>206</div>207"""208 209css = """210:root {211  --g-bg: #f6f9ff;212  --g-panel: rgba(255,255,255,0.86);213  --g-panel-strong: rgba(255,255,255,0.96);214  --g-line: #dbe7f6;215  --g-ink: #15233d;216  --g-sub: #5f728f;217  --g-accent: #1f6fe5;218  --g-accent-2: #0f9bb9;219}220.gradio-container {221  background:222    radial-gradient(circle at top left, rgba(55,120,246,0.10), transparent 32%),223    radial-gradient(circle at bottom right, rgba(15,155,185,0.10), transparent 28%),224    var(--g-bg);225}226.hero-wrap {227  display: grid;228  grid-template-columns: 280px 1fr;229  gap: 28px;230  padding: 22px 8px 12px 8px;231  align-items: center;232}233.hero-logo-card {234  background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(247,250,255,0.90));235  border: 1px solid var(--g-line);236  box-shadow: 0 18px 42px rgba(31,58,114,0.08);237  border-radius: 28px;238  padding: 24px;239  display: flex;240  justify-content: center;241  align-items: center;242  min-height: 170px;243}244.hero-logo {245  width: 100%;246  max-width: 220px;247  object-fit: contain;248}249.hero-right h1 {250  font-size: 2.5rem;251  margin: 0;252  color: var(--g-ink);253}254.hero-copy {255  color: var(--g-sub);256  font-size: 1.06rem;257  line-height: 1.6;258  max-width: 780px;259}260.eyebrow {261  color: var(--g-accent);262  font-size: 0.92rem;263  letter-spacing: 0.14em;264  font-weight: 700;265  margin-bottom: 8px;266}267.hero-chips {268  display: flex;269  flex-wrap: wrap;270  gap: 10px;271  margin-top: 14px;272}273.hero-chips span {274  background: rgba(239,246,255,0.96);275  border: 1px solid #cfe0fb;276  color: #28558f;277  border-radius: 999px;278  padding: 8px 12px;279  font-size: 0.9rem;280  font-weight: 600;281}282.card-note {283  color: var(--g-sub);284}285.block-panel {286  background: var(--g-panel);287  border: 1px solid var(--g-line);288  border-radius: 22px;289  padding: 10px;290}291footer {visibility: hidden;}292@media (max-width: 900px) {293  .hero-wrap {grid-template-columns: 1fr;}294}295"""296 297with gr.Blocks(298    title=f"{APP_TITLE} Studio",299    fill_width=True,300    css=css,301    theme=gr.themes.Soft(302        primary_hue="blue",303        secondary_hue="cyan",304        neutral_hue="slate",305        radius_size="lg",306    ),307) as demo:308    gr.HTML(hero_html)309 310    with gr.Tabs():311        with gr.Tab("GravityLLM Studio"):312            with gr.Row():313                with gr.Column(scale=11):314                    example_name = gr.Dropdown(315                        choices=list(EXAMPLE_FILES.keys()),316                        value="Club Drop",317                        label="Example payload",318                    )319                    load_btn = gr.Button("Load Example", variant="secondary")320                    payload_box = gr.Code(321                        value=load_example("Club Drop"),322                        language="json",323                        label="Constraint + stem feature payload",324                        lines=26,325                    )326 327                with gr.Column(scale=6):328                    model_id = gr.Textbox(329                        value=DEFAULT_MODEL_ID,330                        label="Model repo or endpoint",331                        info="Set your Hugging Face model repo id or inference endpoint URL.",332                    )333                    backend = gr.Dropdown(334                        choices=["hybrid", "remote-model", "rules-engine demo"],335                        value=DEFAULT_BACKEND if DEFAULT_BACKEND in {"hybrid", "remote-model", "rules-engine demo"} else "hybrid",336                        label="Backend",337                    )338                    temperature = gr.Slider(0.0, 1.2, value=0.2, step=0.05, label="Temperature")339                    top_p = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-p")340                    max_new_tokens = gr.Slider(128, 1400, value=900, step=16, label="Max new tokens")341                    use_grammar = gr.Checkbox(342                        value=True,343                        label="Use JSON schema grammar when remote backend supports it",344                    )345                    run_btn = gr.Button("Generate Spatial Scene", variant="primary")346                    status = gr.Textbox(label="Status", interactive=False)347 348            with gr.Row():349                with gr.Column(scale=9):350                    output_box = gr.Code(language="json", label="Generated Spatial9Scene JSON", lines=26)351                    download = gr.File(label="Download scene JSON")352                with gr.Column(scale=7):353                    summary = gr.Markdown("### Ready\n\nLoad an example or paste your own payload.")354                    plot = gr.Plot(label="Spatial scene preview")355 356            object_table = gr.Dataframe(357                headers=["id", "class", "az_deg", "el_deg", "dist_m", "width", "gain_db"],358                datatype=["str", "str", "number", "number", "number", "number", "number"],359                row_count=(0, "dynamic"),360                col_count=(7, "fixed"),361                label="Object inspector",362            )363 364        with gr.Tab("Prompt Builder"):365            gr.Markdown("Build a starter payload, then send it to GravityLLM Studio.")366            with gr.Row():367                target_format = gr.Dropdown(["iamf", "binaural", "5.1.4", "7.1.4"], value="iamf", label="Target format")368                style = gr.Dropdown(["club", "cinematic", "podcast", "live", "intimate"], value="club", label="Style")369                section = gr.Dropdown(["intro", "verse", "break", "drop", "full"], value="drop", label="Section")370            with gr.Row():371                bpm = gr.Slider(0, 200, value=128, step=1, label="BPM")372                energy = gr.Slider(0.0, 1.0, value=0.92, step=0.01, label="Energy")373                max_objects_builder = gr.Slider(1, 32, value=10, step=1, label="Max objects")374            build_btn = gr.Button("Build Payload", variant="primary")375            builder_output = gr.Code(language="json", label="Starter payload", lines=24)376            send_to_studio_btn = gr.Button("Send to Studio", variant="secondary")377 378        with gr.Tab("Validate Existing Scene"):379            scene_input = gr.Code(language="json", label="Paste a Spatial9Scene JSON", lines=24)380            validate_btn = gr.Button("Validate Scene", variant="primary")381            validate_summary = gr.Markdown()382            validate_plot = gr.Plot()383            validate_table = gr.Dataframe(384                headers=["id", "class", "az_deg", "el_deg", "dist_m", "width", "gain_db"],385                datatype=["str", "str", "number", "number", "number", "number", "number"],386                row_count=(0, "dynamic"),387                col_count=(7, "fixed"),388                label="Validated object inspector",389            )390 391        with gr.Tab("About"):392            gr.Image(value=str(ASSETS / "gravityllm_space_banner.png"), label="GravityLLM banner", show_download_button=False, show_fullscreen_button=False)393            gr.Markdown(394                """395### What this Space does396 397- Turns **constraints + stem descriptors** into **Spatial9Scene JSON**398- Can call a remote Hugging Face model repo through `InferenceClient`399- Falls back to a deterministic **rules engine** so the demo stays usable400- Validates outputs against the included JSON schema401- Renders a spatial top-down preview of object positions402 403### Environment variables404 405- `GRAVITYLLM_MODEL_ID` — model repo id or endpoint URL406- `HF_TOKEN` — required if the model is gated or private407- `GRAVITYLLM_BACKEND` — optional default: `hybrid`, `remote-model`, or `rules-engine demo`408 409### Recommended setup410 4111. Upload your GravityLLM model repo.4122. Train and push weights.4133. Upload this Space repo.4144. Set `GRAVITYLLM_MODEL_ID` in the Space settings.415                """416            )417 418    load_btn.click(fn=load_example, inputs=example_name, outputs=payload_box)419    build_btn.click(420        fn=build_payload,421        inputs=[target_format, style, section, bpm, energy, max_objects_builder],422        outputs=builder_output,423    )424    send_to_studio_btn.click(fn=lambda x: x, inputs=builder_output, outputs=payload_box)425    run_btn.click(426        fn=generate_scene,427        inputs=[payload_box, model_id, backend, temperature, top_p, max_new_tokens, use_grammar],428        outputs=[output_box, summary, plot, object_table, download, status],429    )430    validate_btn.click(431        fn=validate_only,432        inputs=scene_input,433        outputs=[validate_summary, validate_plot, validate_table],434    )435 436if __name__ == "__main__":437    demo.launch()438