APNext H3 Music Video Writer
A whole music video from one audio file
- audio
- llm
- image_1
- image_2
- image_3
- image_4
- image_5
- image_6
- image_7
- image_8
- image_9
- vocals
- scenes
- durations
- lengths
- audio_segments
- segment_table
- scenes_text
- synopsis
- cast
- scene_count
- song_seconds
- session_id
- info
- image_1
- image_2
- image_3
- image_4
- image_5
- image_6
- image_7
- image_8
- image_9
- clip_starts
- project_name
The moment you realise it: APNext H3 Music Video Writer turns a song into a music video. Not a mood video with a soundtrack slapped on - a video where every clip is cut on the music, every rendered piece is exactly as long as its audio slice, the performer lip-syncs the actual lyric lines, and the whole thing stitches back together against the original track. It's the most ambitious node in the H3 family, and it mostly works because it's ruthless about one thing: audio lengths and clip lengths must match exactly.
Here's the mechanism. H3 renders clips up to about 15 seconds, so the node cuts the song into consecutive pieces inside min_segment_seconds–max_segment_seconds. The cut points come from the music itself - spectral-flux onsets, energy steps, section changes, and (when you supply timed lyrics) right before a lyric line. Then every piece length is snapped to H3's frame grid (5 + 17k frames at 24fps) so each rendered clip is exactly as long as its audio slice. That's the detail that makes the final stitched video never drift out of sync - which is the failure mode that kills every hand-rolled attempt at this.
Then it writes one scene per piece, each a four-section H3 prompt where the piece is <Audio 1>, reused 1:1 as the clip's soundtrack. In Performance mode the singer lip-syncs the piece's lyric lines on camera (<Subject 1> sings <d>[English] exact line</d> in sync with <Audio 1>); Narrative answers the lyric with pictures; Mixed alternates. Quiet pieces get long intimate shots; loud or peak pieces get more cuts and the chorus look. Long songs are written in chunks of six scenes that continue one session, so the synopsis, wardrobe and location locks stay consistent.
The inputs that matter
audio- the song (from Load Audio). Everything flows from this.direction- the concept: who performs, where, the look, the story arc, what the chorus looks like vs the verses.lyrics- one line per line. Timestamps make the sync exact:[0:15] line,0:15 line, or LRC[00:15.20] line;[Chorus]-style tags are kept. Untimed lines get spread evenly (approximate). Empty = instrumental.performance_mode- Performance / Narrative / Mixed.segment_mode- Auto cuts on the music inside the allowed range; Fixed takes the longest allowed piece each time; Lyric lines tries hardest to cut right before a line.cast_1..4/extra_cast- the performer(s). An H3 Characters node in ✏️ custom mode with awardrobeis made for this - a described performer with a locked outfit.wardrobe/locations/enforce_wardrobe- the same locks as the Crossover Writer (cast-carried wardrobe is merged in).- The usual
visual_style,dialogue_language(the lyric language),wildness, and the Claude Code block, plusimage_1..9reference pictures.
The outputs are matched lists, which is the whole architecture: scenes → the video node's prompt, lengths (frame counts) → length, audio_segments → ref_audio_1, durations → your frame math. Plus segment_table - the cut list, 01 0:00.00 – 0:15.08 (15.08s, 362 frames) energy: peak lyrics: … - and scenes_text, synopsis, cast, scene_count, song_seconds, session_id, info.
The finishing move
Each piece renders as its own clip, so the last step is H3 Scenes Join with replace_audio set to the original song - the joined per-scene audio gets swapped for the real track, and one Create Video → Save Video writes a single music video that plays in sync. The pack ships examples/h3/h3_music_video.json end to end.
Installing
Part of dagthomas/comfyui_dagthomas: ComfyUI Manager → comfyui_dagthomas, or clone into custom_nodes + pip install -r requirements.txt, restart. Under APNext → H3.
Claude Code must be installed and logged in - the default engine, subscription seat and all - unless you connect an H3 LLM Backend to the llm socket and run it on Ollama instead. Budget for the slow end: a 3-minute song is a dozen-plus pieces, each a full Claude turn, plus the audio analysis. Where people get burned: forgetting timed lyrics and wondering why the "sync" is approximate, and skipping replace_audio and wondering why the final video's sound doesn't match the song. Both are one input away from right.
Inputs (69)
| Name | Type | Default | Description |
|---|---|---|---|
| audio | AUDIO | The song. It is cut into 5-15 s pieces and every piece becomes one scene / one clip. | |
| direction | STRING | The music-video concept: who performs, where, the look, the story arc, recurring motifs, what the chorus looks like vs the verses. Free text. | |
| lyrics | STRING | Lyrics, one line per line. Timestamps make the sync exact: `[0:15] line`, `0:15 line` or LRC `[00:15.20] line`; section tags like [Chorus] are kept. Untimed lines are timed automatically from a Whisper pass when transcribe_lyrics is on (1:1 when the line counts match, else spread over the sung span); with transcription off they are spread evenly over the WHOLE song and can land in instrumental passages - a singer mouthing words nobody sings. Empty = take the `lyrics_in` socket, else transcribe the song (transcribe_lyrics), else an instrumental video. Typed text always wins over the socket. | |
| performance_mode | COMBO | Performance (the singer lip-syncs the lyrics on camera) | 3 options: Performance (the singer lip-syncs the lyrics on camera), Narrative (story visuals, nobody sings on camera), Mixed (performance and story, alternate or blend) |
| segment_mode | COMBO | Auto (cut on the music) | How the song is cut. Auto cuts on onsets / energy changes inside the allowed length range (lyric-line starts are preferred when lyrics are timed); Fixed takes the longest allowed piece every time; Lyric lines tries hardest to cut right before a line. |
| max_segment_seconds | FLOAT | 15.05.2–15.1 | Longest piece (H3 renders up to ~15 s). Lengths snap to H3's frame grid. |
| min_segment_seconds | FLOAT | 5.25.2–15 | Shortest piece. 124 frames (~5.2 s) is the shortest trained clip. |
| shots_per_scene | COMBO | Auto | Shots per scene. Auto lets the model cut to the music (more shots in loud parts). |
| visual_style | COMBO | Live-action, cinematic | Opens every [Shot 1]; kept identical across the whole video. |
| dialogue_language | COMBO | English | Language of the lyrics (the <d>[...] tag). |
| model | COMBO | sonnet | Who writes the prompt. sonnet / opus / haiku / fable / default are Claude Code aliases (`default` = whatever the CLI is configured for). `codex` is the OpenAI Codex CLI with its configured model (shown when installed; `codex:<model-id>` in an H3 LLM Backend picks a specific one). ollama: / lmstudio: / local: entries are whatever your local servers were serving when the page loaded; pick one to run fully offline. Anything not listed goes in model_override. |
| research | BOOLEAN | false | Let the agent CLI (Claude Code or Codex) search the web for real references before writing - the actual location, wardrobe, lighting and physics. Slower, and it reaches the internet. |
| director | BOOLEAN | true | Load the H3 director skills (data/h3/skills): the core writing rules, the format this node emits, and style/motion craft, each with a reference library of gold examples and style anchors that Claude Code reads on demand. Costs a few extra seconds and tokens per run. |
| use_subscription | BOOLEAN | true | Hide the API key from the CLI so it uses your login and subscription seat (ANTHROPIC_API_KEY for Claude Code, OPENAI_API_KEY for Codex). Turn off to bill the API key instead. |
| timeout_seconds | INT | 120060–7200 | How long to wait PER CALL before the node gives up on the CLI (this is the node's own watchdog, not a Claude limit). Single H3 prompts take 25-60s; a multi-scene chunk with director/research on can take 10-20 minutes. The multi-scene writers retry a timed-out chunk at half size automatically. |
| seed | INT | -1-1–18446744073709550000 | Controls caching. -1 re-runs every queue. |
| cast_1opt | STRING | A cast line or block from an H3 Characters node (its `cast` output): the performer(s) and anyone else in the video. | |
| cast_2opt | STRING | A cast line or block from an H3 Characters node (its `cast` output): the performer(s) and anyone else in the video. | |
| cast_3opt | STRING | A cast line or block from an H3 Characters node (its `cast` output): the performer(s) and anyone else in the video. | |
| cast_4opt | STRING | A cast line or block from an H3 Characters node (its `cast` output): the performer(s) and anyone else in the video. | |
| extra_castopt | STRING | Performer(s) / characters typed by hand, one per line, e.g. `Lead singer: a woman in her 30s with a platinum pixie cut` or `Character (played by Actor) from Show`. Merged with the cast sockets. | |
| custom_dialogue_languageopt | STRING | — | |
| custom_visual_styleopt | STRING | Any visual style not in the dropdown; overrides the dropdown when filled in. | |
| wardrobeopt | STRING | Wardrobe lock, one line per CAST member, e.g. `Sheldon: brown corduroy jacket, green Flash T-shirt, khaki trousers, small silver ring in the left nostril`. Used word-for-word in subject_definitions and at the character's first appearance in each scene; later shots carry it on the `<Subject N>` label, as H3's guide specifies. Empty = Claude fixes one outfit per cast member itself (in the synopsis) and reuses it. Only the cast is locked - extras and background people are described where they appear and never carry an anchor set. | |
| locationsopt | STRING | Location lock, one line per recurring place, e.g. `Sheldon's living room: beige three-seat sofa facing a wall-mounted TV on the LEFT, tall bookshelf of comics behind it, bay window with white blinds on the RIGHT, warm tungsten floor lamp in the far corner`. Used word-for-word in every scene set there, so the room looks the same in every scene. Empty = the model fixes each recurring place itself (in the synopsis) and repeats it. | |
| enforce_wardrobeopt | BOOLEAN | true | After writing, check that each character's FIRST appearance in a scene states all of that character's wardrobe anchors verbatim, and that every scene set in a locked location restates that location's anchors. If anything is dropped or changed, the model gets one repair turn in the same session. Off = trust the first answer. |
| extra_instructionsopt | STRING | — | |
| image_notesopt | STRING | Per-picture notes, one per line: `Image 1: the singer`, `Image 2: the rooftop, use as the location`. With reference_image_use = Characters only (the default), a note like that is the ONLY way a picture may be read as a location or prop - otherwise every picture is a performer and its backdrop is ignored. | |
| resume_session_idopt | STRING | Continue an earlier run by feeding it that node's session_id. The whole conversation, images included, is still in context. A session sticks to its backend: Claude Code ids resume with Claude Code, `codex-` ids with Codex, `local-` ids with the same local model. | |
| working_diropt | STRING | A folder Claude Code may read while writing - a script, a shot list, lookbook notes. Empty uses a throwaway scratch folder, which is the safe default. | |
| llmopt | APNEXT_LLM | Optional. Connect an APNext H3 LLM Backend node to write with Ollama, LM Studio, another OpenAI-compatible server or an API model instead of Claude Code. Overrides the model dropdown while connected. | |
| context_1opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| context_2opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| context_3opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| context_4opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| context_5opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| context_6opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| context_7opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| context_8opt | STRING | Steering input from another APNext node (Time, Scene, Poses, Plots, Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). The node detects which kind it is and tells Claude how to use it. Sockets grow as you connect them. | |
| image_1opt | IMAGE | Reference image 1: <Picture 1> in the prompt. Connect the same image to image_1 on the MiniMax H3 Reference to Video node, or use this node's image_1 output. | |
| image_2opt | IMAGE | Reference image 2: <Picture 2> in the prompt. Connect the same image to image_2 on the MiniMax H3 Reference to Video node, or use this node's image_2 output. | |
| image_3opt | IMAGE | Reference image 3: <Picture 3> in the prompt. Connect the same image to image_3 on the MiniMax H3 Reference to Video node, or use this node's image_3 output. | |
| image_4opt | IMAGE | Reference image 4: <Picture 4> in the prompt. Connect the same image to image_4 on the MiniMax H3 Reference to Video node, or use this node's image_4 output. | |
| image_5opt | IMAGE | Reference image 5: <Picture 5> in the prompt. Connect the same image to image_5 on the MiniMax H3 Reference to Video node, or use this node's image_5 output. | |
| image_6opt | IMAGE | Reference image 6: <Picture 6> in the prompt. Connect the same image to image_6 on the MiniMax H3 Reference to Video node, or use this node's image_6 output. | |
| image_7opt | IMAGE | Reference image 7: <Picture 7> in the prompt. Connect the same image to image_7 on the MiniMax H3 Reference to Video node, or use this node's image_7 output. | |
| image_8opt | IMAGE | Reference image 8: <Picture 8> in the prompt. Connect the same image to image_8 on the MiniMax H3 Reference to Video node, or use this node's image_8 output. | |
| image_9opt | IMAGE | Reference image 9: <Picture 9> in the prompt. Connect the same image to image_9 on the MiniMax H3 Reference to Video node, or use this node's image_9 output. | |
| scenes_from_lyricsopt | BOOLEAN | false | Build the whole video from the lyrics: the song is cut where lyric phrases start (needs timestamped lyrics like `[0:15] line`; without timestamps it falls back to the segment mode above) and every scene's imagery is written from its lyric lines - the pictures stage what the words say, while the concept supplies style, palette and motifs. Instrumental stretches still cut on the music. |
| reference_image_useopt | COMBO | Characters only (ignore picture backgrounds) | How the reference pictures may be read. Characters only: every picture is a character/performer reference - identity (and wardrobe) carries over, the photo's background, setting and lighting are ignored, and no scene or location is ever derived from them; a picture note can still declare a specific image a location or prop. Auto: the model decides what each picture shows (a backdrop can become the scene). |
| scene_briefsopt | STRING | Manually planned scenes from chained H3 Scene Brief nodes: each brief (what happens, where, which cast members and pictures) becomes the binding plan for its scene/piece. Pinned numbers take that piece; unpinned briefs fill in order; pieces without a brief stay the model's to invent. | |
| audio_modeopt | COMBO | Reference audio (<Audio 1> = the song piece) | How the song reaches the video model. Reference audio: each piece is attached as ref_audio_1 and the prompts define <Audio 1> (classic Ref2VA; lip-sync is a strong suggestion). Masked latent: for workflows that write the song slice straight into the H3 audio latent and protect it from denoising (e.g. `H3 Song Audio + Masked Video Context` fed by this node's `clip_starts`) - the prompts then reference the protected master-song audio and define no <Audio N>, and lip-sync is enforced by the model itself. Do not wire audio_segments to ref_audio in that setup. |
| save_scenesopt | BOOLEAN | true | Store every successful run as a JSON bundle in output/apnext_scenes/ (scenes, synopsis, segment times, durations, clip starts, cast). Reload it any time with APNext H3 Scenes Load - re-render without paying for the LLM again. |
| scenes_per_callopt | INT | 41–8 | How many scenes to ask the model for per call. Smaller chunks finish well inside timeout_seconds and fail smaller (a timed-out chunk is retried at half size automatically); larger chunks are slightly cheaper per scene. 1 = every scene written in its own call. |
| prompt_modeopt | COMBO | Ref2VA (bind reference images) | Which official prompt guide the scenes follow, and whether the WRITING model is shown the reference pictures. Ref2VA (guide_ref_en.md): pictures are bound as <Picture N> and also sent to the writer, so it describes the real face. Needs a vision-capable model. Ref2VA blind: the video model still receives every picture under the same label, but the writer never sees one - it takes who is in each picture from the cast lines and image_notes. Use it with any model WITHOUT vision (a text-only local model, an uncensored fine-tune), and write an `Image 1: ...` note per picture. FL / T2VA (guide_base_en.md): everything from scratch in words. Pictures are ignored entirely and no <Picture N> label is written - they still pass through the image outputs. Auto picks Ref2VA when pictures are connected, FL otherwise. |
| draft_modelopt | COMBO | haiku | Who DRAFTS the scene chunks. The main `model` stays the director: it plans the video (synopsis, wardrobe/location locks, scene plan) and runs the continuity repair, while this model writes the scenes from that plan. haiku drafts several times faster than sonnet. `same as model` turns the split off. Ignored when the run is backed by Codex, a local server or an H3 LLM Backend override - those runs use one model throughout. |
| parallel_chunksopt | BOOLEAN | true | Write the scene chunks concurrently instead of one after another: one planning call (by `model`) fixes the synopsis, the locks and a per-scene plan, then up to 4 chunks at a time are drafted from that plan (by `draft_model`), and one continuity pass repairs any drift. Much faster for long songs. Off = the classic serial run where every chunk continues one session. Ignored when resume_session_id is set or the whole song fits in one call. |
| project_nameopt | STRING | A tag for this run - auto-filled with a random name like 'NeonDollyFoley-7k3q' when the node is created; type your own to rename the project. Wire the node's `project_name` output into Save Video's `filename_prefix` and every clip of the run lands in its own subfolder (output/video/<name>/), so the output folder shows at a glance which videos belong together. Saved scene bundles carry it too. Empty = a fresh random name each run (stable when `seed` is fixed). | |
| transcribe_lyricsopt | BOOLEAN | true | When the lyrics box is empty, transcribe the song with Whisper (openai/whisper-large-v3-turbo via transformers; ~1.6 GB download on first use) into timed `[m:ss] line` lyrics, so the scenes still sing the real words and lyric-timed cuts work. Wire a vocal stem into `vocals` for a cleaner transcription than the full mix. |
| cut_planopt | STRING | The `cut_plan` output of an APNext H3 Cut Plan node (or hand-typed lines shaped `01 0:00.00 - 0:12.96`). The video is then cut into EXACTLY these scenes and segment_mode / max_segment_seconds / min_segment_seconds are ignored. Decide the scenes once, see them, edit them, then write. | |
| sound_eventsopt | STRING | The `events` (or `events_json`) output of an APNext H3 Sound Events node. Every piece's brief then carries the bass hits, impacts, drops, builds and stops that land INSIDE that clip, timed from the clip's own start, and the model is told to stage the picture on them - a cut, a camera hit, a light change, a move that lands on the beat. Leave it unconnected and the scenes are written from the song's energy profile alone, as before. | |
| beat_gridopt | STRING | The `grid_json` output of an APNext H3 Beat Grid node: its tempo, every beat and the downbeats (phase-fitted to the Sound Events hits, with your BPM override). Every piece's brief then lists ITS beats from this grid - `[beat] every 0.469 s at +0.00 +0.47 ...` - instead of the writer's own rougher measurement. Leave it unconnected and the beats come from the song structure the writer measures itself. | |
| lyrics_inopt | STRING | Lyrics from another node - the `lyrics` output of an APNext H3 Lyrics Transcribe node, or any STRING. Used only while the `lyrics` box above is empty: anything typed there wins, so Whisper's words can be corrected by hand without unplugging the socket. | |
| vocalsopt | AUDIO | Optional vocal stem (e.g. AudioSeparation's vocals output) used only for lyric transcription - much cleaner than transcribing the full mix. Ignored when lyrics are typed in or transcribe_lyrics is off. | |
| lyric_interpretationopt | COMBO | Auto (the writer decides per song) | How far the pictures may stray from the words. The lyric is always audible and sung as performance_mode says; this decides what the PICTURE does with it. Literal shows what the words say. Loose stages the feeling, never the nouns. Metaphor builds the whole video on one image system that is not the lyric. Counterpoint tells a different story that rhymes with the song. Reframe keeps the subject but moves it into another world, era or genre. Surreal is dream logic that escalates. Surprise me picks one of the non-literal readings by seed, so every re-run is a different video. Auto leaves it to the writer. |
| transition_styleopt | COMBO | Reveal (a camera move discloses a new space around them) | What a CONTINUING scene may do with its place. The chain carry keeps the actor exactly - pose, direction, speed - but not the previous place, so a change of place inside a take has to be written. Auto: the writer picks walk-through or reveal per scene where the plan moves. Stay: same place, always. Walk-through: the actor carries the take through a door / corridor / corner into the new place, camera following. Reveal: the actor holds, the camera moves, and a new space is disclosed around them. New place: every scene gets its own place - continuing scenes move there inside the take (walk-through or reveal, alternating), so no two consecutive scenes share a location. Hard cuts are unaffected. |
| run_modeopt | COMBO | write (call the LLM) | Continue from the scenes you already have. Reuse last run: skip the LLM and return the newest bundle this node saved to output/apnext_scenes/ (`save_scenes`) - the same scenes, lengths, clip starts and project name - so everything downstream renders again with other sampler settings, seeds or references. While it is on, edits to the direction, lyrics or cast are ignored; set it back to write for a new script. For an older run pick the file in APNext H3 Scenes Load. |
| continuityopt | COMBO | cut plan decides (continue over soft cuts, hard cut on drops / sections / taps) | Which pieces are written as the SAME take carrying on and which open on a hard cut. Cut plan decides: soft cuts (onset / downbeat / lyric line) continue, drops, section starts, stops and taps cut. Flow everywhere: one continuous take. Cut everywhere: every scene is its own clip - a fresh setup at every cut, nothing carried over. Set H3 Chain Render's `continuity` to the same value so the render matches what was written. |
| session_historyopt | COMBO | Windowed (brief + previous chunk - fits long serial runs) | Serial runs on a LOCAL model (ollama:/lmstudio:/local:) replay the whole session on every chunk, so context grows with every scene until num_ctx bursts. Windowed always replays the first exchange (the brief and its synopsis reply) plus AS MANY recent chunks verbatim as num_ctx affords - a short song still sees everything; on a long one the chunks that no longer fit are folded into a running recap (one small extra LLM call) that rides along, on top of the one-line story-so-far gists every chunk gets. Full is the old replay-everything behaviour; fine for short songs or big num_ctx. Claude Code / API models ignore this. |
Outputs (23)
| Name | Type | Description |
|---|---|---|
| scenes | STRING | — |
| durations | FLOAT | — |
| lengths | INT | — |
| audio_segments | AUDIO | — |
| segment_table | STRING | — |
| scenes_text | STRING | — |
| synopsis | STRING | — |
| cast | STRING | — |
| scene_count | INT | — |
| song_seconds | FLOAT | — |
| session_id | STRING | — |
| info | STRING | — |
| image_1 | IMAGE | — |
| image_2 | IMAGE | — |
| image_3 | IMAGE | — |
| image_4 | IMAGE | — |
| image_5 | IMAGE | — |
| image_6 | IMAGE | — |
| image_7 | IMAGE | — |
| image_8 | IMAGE | — |
| image_9 | IMAGE | — |
| clip_starts | FLOAT | — |
| project_name | STRING | — |