Single-pass final and finishing post-steps (#54) #109

Merged
crowmaki merged 3 commits from issue-54-single-pass-final into main 2026-10-09 01:31:21 +00:00
Owner

Closes #54. Part of #24 (User Stories 120, 135, 141 and 142).

What changes

A filmmaker can render a final straight away and get a real ProRes master.

  • Single-pass final. The queue dialog has a Quality picker: Draft or Single-pass final. A final runs h3.final in one pass at the profile's final short side (1120 by default), with the same references, prompt and LoRAs as a draft. It saves no latent. Its Rendition is kind = final, via = single_pass. It is a final job, so it runs after drafts and Hold finals holds it.
  • Post-steps. Collecting a Rendition now only writes its job's files into the media folder. Its post-steps then finish it in their own CPU lanes, two at a time, outside the Project lock and the GPU lane.
    • Every Rendition gets its poster frame here. Drafts used to get it under the Project lock.
    • A final's ProRes 422 HQ master from the workflow is remuxed with a 01:00:00:00 timecode and the job's FLAC as 24-bit PCM. The result is renditions/<id>.mov.
    • An H.264 proxy at short side 768 (never upscaled) goes to proxies/<id>.mp4. It uses h264_nvenc when a startup check (a one-frame NVENC encode) works, otherwise libopenh264.
    • The app plays the proxy.
    • The Take shows Finishing until the post-steps are done.
  • Post-step failure. The GPU output is kept, and the Take reads Proxy missing ("Post-steps failed" for a draft), with the reason. Opening the Take shows Retry post-steps, which runs them again from the kept files (retry_post_steps). If stamping fails, no proxy is made, because a proxy of the unstamped master would have no sound.
  • Recovery. Post-steps that were running when the Project closed run again when it reopens.
  • Preparation overlap. Once a job is submitted, the worker uploads the references of the job it expects to take next while ComfyUI renders. It does not upload them again when it takes that job. Uploads are named by content hash, so a prepared name always holds the same bytes.
  • Schema. V26 adds four columns to rendition: gpu_video_path, proxy_path, post_state (pending / running / failed) and post_error.
  • CONTEXT.md defines Post-steps (and proxy).

Tests

  • ffmpeg.rs, with real ffmpeg:
    • the stamped master has timecode 01:00:00:00, ProRes HQ video and pcm_s24le audio
    • the libopenh264 proxy is H.264 at 1536×768 with AAC
    • a small master keeps its size
    • the startup check falls back to OpenH264, and the encoder it picks really makes a proxy (NVENC on this machine)
  • tests/takes.rs:
    • the final graph: h3.final, 1984×1120, no SaveLatent, the same slots, ProRes output, final class, held by Hold finals
    • a final rendered against the fake ComfyUI goes through Finishing and lands as a stamped master plus proxy, with no latents
    • post-step failure keeps the master, marks the Take and refuses a retry of a Rendition that did not fail. After the audio is fixed, a retry makes the proxy.
    • post-steps cut off by a close run again on reopen
    • the next job's references upload while the current job renders, and are not uploaded twice
  • The fake ComfyUI names ProRes output .mov.

cargo test --workspace --all-features (635 passed), bun test src scripts (161 passed) and bun run check all pass.

Note on ffmpeg: the tests need an ffmpeg with libopenh264, as the LGPL sidecar will have. The ffmpeg on PATH here has no libopenh264, so I ran the suite with AI_STUDIO_FFMPEG pointing at a BtbN LGPL 8.1 build (outside the repo). CI or anyone else running the tests needs the same.

Decisions and known gaps

  • Draft posters moved to the post-step lane. This follows Story 135 ("posters run in a CPU lane"), but a draft now shows Finishing briefly before Ready. The spec's "poster frames are extracted when a Rendition is collected" can be read either way.
  • Preparation happens once, right after submission. It covers the job expected next at that moment. A job queued mid-render is not prepared, and a job being recovered prepares nothing. Uploads run on the worker thread, so progress events wait until they finish. This is fine on loopback, but large Atlases could delay the progress bar a little.
  • A ProRes master does not play in the webview. While a final's proxy is missing, the player has nothing it can decode. The marker and Retry are the way out.
  • The marker and Retry look only at a Take's newest Rendition. That is enough until Promotion (#57) gives a Take a second Rendition.
  • Not done (review judgement calls): a PostState enum instead of strings, and an amber tone for "Proxy missing", which ADR-0006 reserves for stale/held/unlocked. Badge.svelte's comment also allows "needs-attention". Please say if you want either changed.

Screenshots

The queue dialog with Single-pass final chosen.
Queue a Single-pass final

A landed final: its post-steps ran in the real app (staged GPU output), and the player streams the H.264 proxy.
Final ready

A final whose post-steps failed (broken audio): Proxy missing, the reason and Retry post-steps. Retry then made the proxy in the app. This screenshot is from before a copy tweak: the line now says "The render is kept…".
Proxy missing

Closes #54. Part of #24 (User Stories 120, 135, 141 and 142). ## What changes A filmmaker can render a final straight away and get a real ProRes master. - **Single-pass final.** The queue dialog has a Quality picker: Draft or Single-pass final. A final runs `h3.final` in one pass at the profile's final short side (1120 by default), with the same references, prompt and LoRAs as a draft. It saves no latent. Its Rendition is `kind = final`, `via = single_pass`. It is a final job, so it runs after drafts and Hold finals holds it. - **Post-steps.** Collecting a Rendition now only writes its job's files into the media folder. Its **post-steps** then finish it in their own CPU lanes, two at a time, outside the Project lock and the GPU lane. - Every Rendition gets its poster frame here. Drafts used to get it under the Project lock. - A final's ProRes 422 HQ master from the workflow is remuxed with a `01:00:00:00` timecode and the job's FLAC as 24-bit PCM. The result is `renditions/<id>.mov`. - An H.264 proxy at short side 768 (never upscaled) goes to `proxies/<id>.mp4`. It uses `h264_nvenc` when a startup check (a one-frame NVENC encode) works, otherwise `libopenh264`. - The app plays the proxy. - The Take shows **Finishing** until the post-steps are done. - **Post-step failure.** The GPU output is kept, and the Take reads **Proxy missing** ("Post-steps failed" for a draft), with the reason. Opening the Take shows **Retry post-steps**, which runs them again from the kept files (`retry_post_steps`). If stamping fails, no proxy is made, because a proxy of the unstamped master would have no sound. - **Recovery.** Post-steps that were running when the Project closed run again when it reopens. - **Preparation overlap.** Once a job is submitted, the worker uploads the references of the job it expects to take next while ComfyUI renders. It does not upload them again when it takes that job. Uploads are named by content hash, so a prepared name always holds the same bytes. - **Schema.** V26 adds four columns to `rendition`: `gpu_video_path`, `proxy_path`, `post_state` (`pending` / `running` / `failed`) and `post_error`. - **CONTEXT.md** defines **Post-steps** (and proxy). ## Tests - `ffmpeg.rs`, with real ffmpeg: - the stamped master has timecode `01:00:00:00`, ProRes HQ video and `pcm_s24le` audio - the `libopenh264` proxy is H.264 at 1536×768 with AAC - a small master keeps its size - the startup check falls back to OpenH264, and the encoder it picks really makes a proxy (NVENC on this machine) - `tests/takes.rs`: - the final graph: `h3.final`, 1984×1120, no `SaveLatent`, the same slots, ProRes output, final class, held by Hold finals - a final rendered against the fake ComfyUI goes through Finishing and lands as a stamped master plus proxy, with no latents - post-step failure keeps the master, marks the Take and refuses a retry of a Rendition that did not fail. After the audio is fixed, a retry makes the proxy. - post-steps cut off by a close run again on reopen - the next job's references upload while the current job renders, and are not uploaded twice - The fake ComfyUI names ProRes output `.mov`. `cargo test --workspace --all-features` (635 passed), `bun test src scripts` (161 passed) and `bun run check` all pass. **Note on ffmpeg:** the tests need an ffmpeg with `libopenh264`, as the LGPL sidecar will have. The ffmpeg on `PATH` here has no `libopenh264`, so I ran the suite with `AI_STUDIO_FFMPEG` pointing at a BtbN LGPL 8.1 build (outside the repo). CI or anyone else running the tests needs the same. ## Decisions and known gaps - **Draft posters moved to the post-step lane.** This follows Story 135 ("posters run in a CPU lane"), but a draft now shows Finishing briefly before Ready. The spec's "poster frames are extracted when a Rendition is collected" can be read either way. - **Preparation** happens once, right after submission. It covers the job expected next at that moment. A job queued mid-render is not prepared, and a job being recovered prepares nothing. Uploads run on the worker thread, so progress events wait until they finish. This is fine on loopback, but large Atlases could delay the progress bar a little. - **A ProRes master does not play in the webview.** While a final's proxy is missing, the player has nothing it can decode. The marker and Retry are the way out. - The marker and Retry look only at a Take's newest Rendition. That is enough until Promotion (#57) gives a Take a second Rendition. - Not done (review judgement calls): a `PostState` enum instead of strings, and an amber tone for "Proxy missing", which ADR-0006 reserves for stale/held/unlocked. Badge.svelte's comment also allows "needs-attention". Please say if you want either changed. ## Screenshots The queue dialog with Single-pass final chosen. ![Queue a Single-pass final](https://git.crowmaki.me/attachments/da199443-830e-435d-bf96-fd5943d0f272) A landed final: its post-steps ran in the real app (staged GPU output), and the player streams the H.264 proxy. ![Final ready](https://git.crowmaki.me/attachments/22c31a31-755e-49a0-a9f2-a3fab9fefb47) A final whose post-steps failed (broken audio): Proxy missing, the reason and Retry post-steps. Retry then made the proxy in the app. This screenshot is from before a copy tweak: the line now says "The render is kept…". ![Proxy missing](https://git.crowmaki.me/attachments/fefb75f7-4b87-495b-b77c-e80fc5cb5a4c)
A Take can now be queued as a Single-pass final: h3.final runs in one pass
at the profile's final size and saves no latent. Collecting a Rendition only
stores its job's files; post-steps then finish it in two CPU lanes, off the
Project lock and the GPU lane. Every Rendition gets its poster there. A
final's ProRes master is stamped with a 01:00:00:00 timecode and its FLAC as
PCM, and an H.264 proxy at short side 768 is made with h264_nvenc when a
startup check finds it, else libopenh264. The Take is Finishing until the
post-steps are done. A failed post-step keeps the GPU output, plays it, and
marks the Rendition until the post-steps are retried. Post-steps cut off by
a close run again on reopening.

Once a job is submitted, the worker uploads the next job's references while
ComfyUI renders, and does not upload them again when it takes that job.
The queue dialog picks Draft or Single-pass final. A Take's player says
which Rendition plays, and a Take whose post-steps failed reads Proxy
missing, says why and offers Retry post-steps.
A draft whose post-steps failed reads Post-steps failed rather than Proxy
missing, since it has no proxy. A master that cannot be read leaves its size
and hash empty instead of zero. Comments no longer claim a kept ProRes master
plays in the app. Test the proxy with the encoder the startup check picks, so
the NVENC arguments run where NVENC exists, and share the test tone. Rename
the worker's per-job context so it no longer clashes with the post-step lanes.
crowmaki deleted branch issue-54-single-pass-final 2026-10-09 01:31:21 +00:00
Sign in to join this conversation.
No description provided.