Composition Inference
Composition Inference Picks the Frame Too
- composition_json
- composition_prompt
- width
- height
The Audio Mood Analyzer tells you what to draw. It doesn't tell you how to frame it - and framing is where a mood image lives or dies. A claustrophobic, spiraling song probably shouldn't come out as a wide 16:9 landscape with the subject dead center, and a sparse ambient piece probably shouldn't be a cramped portrait. Composition Inference is the art director sitting downstream of the analyzer: it reads mood_json and subject_json, asks the local LLM to infer the visual composition, and hands you not just prose but parsed width and height integers you can wire directly into EmptyLatentImage.
This is the node that turns the pack from "good prompts" into "good prompts at the right aspect ratio" - and honestly, for anyone who's ever generated a batch at 1:1 and wished they'd thought about the frame, it's the piece that feels like the pack read your mind.
How it works
It runs two Ollama calls. The first (at analysis_temperature, 0.4) asks the model to act as an art director and return a strictly-schema'd composition JSON - aspect ratio, orientation, subject placement, environment weight, camera distance, framing style, crop. The schema is constrained (orientation must be portrait/landscape/square, ratios come from a fixed list, crops from full_body/waist_up/bust/face_only/none), which is what makes the result useful instead of vague. The second call (at prompt_temperature, 0.8) translates that JSON into a one-to-two-sentence prose composition prompt - camera language and negative-space direction you can layer into CLIPTextEncode.
The recommended_resolution from the JSON is parsed back into width and height INT outputs, so the loop closes: the LLM decides the canvas, and the canvas is already set when you reach EmptyLatentImage.
Inputs and the subject signal
Inputs are mood_json and subject_json (from Audio Mood Analyzer), plus the usual ollama_url, model, and the two temperatures. The important design decision: subject analysis is the primary signal. The composition is inferred from the semantic and lyrical content of subject_json first - who the subject is and what the lyrics describe determines placement and camera. If subject_json is empty, the node logs a warning and continues, defaulting toward environment-dominant framing with no crop. So if you want composition to actually engage with the subject, feed it lyrics; if you run it lyricless, you get landscape-first framing, which is the sensible default.
There's a safety net: if the LLM's JSON won't parse, the node falls back to safe defaults - 1024×1024, centered subject, balanced framing - logs a warning, and keeps going. Your run never dies on a malformed model output. (Side note: mood_json and subject_json are marked multiline in the schema, and the node tolerates empty ones gracefully.)
Wiring it
Composition Inference → width/height into EmptyLatentImage, composition_prompt into a CLIPTextEncode as a third conditioning layer alongside the environment and subject prompts. The pack's example workflow (example_composition.json) demonstrates exactly that. If you're using the SDXL-lineage models where the aspect ratio meaningfully changes composition, this is the difference between a good prompt rendered at the wrong shape and a coherent image.
Install
Same pack, same drill:
cd ComfyUI/custom_nodes
git clone https://github.com/andrea-spoldi/ComfyUI-AudioMoodAnalyzer.git
pip install -r ComfyUI-AudioMoodAnalyzer/requirements.txt
Restart, find it under audio/analysis, and have Ollama running (ollama pull qwen3:14b). Two local LLM calls per run, no keys, no cloud - the price of the art director is a few seconds and a couple of tokens per workflow execution.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| mood_json | STRING | — | |
| subject_json | STRING | — | |
| ollama_url | STRING | http://localhost:11434/api/generate | — |
| model | STRING | qwen3:14b | — |
| analysis_temperature | FLOAT | 0.40–1.5 | — |
| prompt_temperature | FLOAT | 0.80–1.5 | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| composition_json | STRING | — |
| composition_prompt | STRING | — |
| width | INT | — |
| height | INT | — |