diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b383df3..b58a3e00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,73 @@ ## [Unreleased] +## [3.5.1] "Auspicious Asparagus" - 2026-08-11 + +FunPack picks up release codenames, Ubuntu-style: an adjective and a vegetable sharing an +initial, one per major version. All of 3.x is **Auspicious Asparagus**. + +Otherwise this is a compatibility-and-polish release on top of 3.5.0's second model family. +LTX-2.5 landed upstream mid-cycle and turned out to need almost nothing — but the two places +it *would* have broken silently are fixed, and several long-standing rough edges went with +them. + +### Added +- **Release codenames.** Shown under the name in Settings ▸ About, carried on the same + payload as the version. A major with no name yet simply shows nothing. +- **About reports the machine ComfyUI runs on.** Chip, memory, GPU (name, VRAM and compute + capability), free disk, OS, ComfyUI, Python, torch and CUDA — plus which fast-attention + backend is actually importable, which answers "is SageAttention really installed on this + box" without reading the launch arguments. On a rental the host and the browser are + different machines and the host is the interesting one, so the panel says which it means. + Every probe degrades to a dash on its own: About is the screen that should still render + when the install is broken. +- **A schedule for every sampler.** Steps and scheduler belong to the pass, not to the + KSampler branch, so any sampler can now be given a computed schedule instead of only a + hand-typed sigma list. The frames field became usable in the same pass. +- **ALG on any sampler.** The i2v anchor blur used to be locked to FunPack's Distilled Flow + sampler. It now runs on whatever sampler is wired — a stock KSampler, Hybrid Euler 2S, a + two-evals-per-step sampler like `heun` — by lifting the guidance out of the sampler loop + and onto a denoiser proxy driven by the step's sigma. +- **H3 audio clock.** MiniMax H3 denoises video and audio on two different flow schedules + but hands the sampler one sigma grid, so the DiT reconciles them with a start-of-step + slope that badly overshoots on a few-step schedule — heard as distortion. `h3_audio_clock` + swaps that for the chord actually spanning the step. One scalar multiply, no extra model + call. It works with stock ComfyUI samplers too, and stands itself down with a console note + on samplers that evaluate twice per step rather than guessing. +- **Projects remember your Editor settings**, so a fresh rental does not reset your + preferences and shortcut revolver. +- **Every Engine setting says what it does, what it costs and what it needs**, in one line. + ### Fixed +- **LTX-2.5 compatibility.** 2.5 reuses the same model classes behind new config flags, so + nearly everything binds unchanged — but two places assumed 2.3 specifics and would have + failed *silently*. The video/audio conditioning split was a hardcoded width table; from + 2.5 on ComfyUI reads those widths off the checkpoint, and an unrecognised pair made the + split return the full width, steering the audio text context along with the video. The + only symptom would have been degraded audio. The split is now measured off the live model, + with the table kept as a fallback. Separately, decode-time noise reached only the conv + decoder: 2.5's diffusion decoder takes no timestep and seeds itself, but is an `nn.Module`, + so the settings were accepted and then ignored — a knob that read as live and did nothing. + It is now capability-tested and says so when it cannot apply. +- **`cut_opening_frames` no longer leaves a noisy first frame.** The cut was made in latent + space, and the LTX video VAE is causal: latent frame 0 is the temporal origin, while every + later frame was generated as a continuation. Slicing the front off the latent promoted a + continuation frame to position 0, which then decoded with origin handling it was never + generated for. The cut now happens on decoded pixels, after a decode that saw every frame + in the context it was sampled in — the way MiniMax H3 already did it. The count was wrong + too: a latent cut could only remove whole latent frames and shortened the clip by a + different amount than the span it removed. It is exact now, N means N. Video comes from the + IMAGES output on a cut run (the Editor already wires it that way); the latent's audio + stream is cropped by the same amount of time so sound and picture still start together. +- **Guide keyframes are dropped when `second_pass_op` changes the resolution.** They are + recorded as token indices into pass 1's grid, so `upscale_2x` left them addressing the + wrong tokens — which recent ComfyUI rejects outright and older builds mis-placed silently. + Pass 1 still uses every guide; only pass 2 loses them, and the scene report says so. +- **One ALG control instead of two.** The Distilled Flow panel had its own switch while the + chain sampler's anchor blur did the same thing on any sampler and already drove it — two + controls for one behaviour, with precedence depending on which you had touched. The Editor + now shows one. Projects carrying the old switch are migrated with their strength and + threshold, and told so. - **H3 image conditioning actually reaches the model.** Two independent breaks, both silent, both ending with a generation that ignored the input image entirely and left the prompt to carry the whole shot. The Movie Editor always splits a run into per-scene conditionings, @@ -16,12 +82,26 @@ because the sampler's positive comes from Studio. New optional **Chain Sampler · h3_keyframes** input (auto-wired for H3 projects) salvages the pins — first frame onto the opening scene, last frame re-indexed onto the closing scene's own final frame. +- **Spare H3 token tags are trimmed, not thrown away.** An image prompt comes back one tag + long, and treating the surplus as corruption discarded the whole vector — which on exactly + those prompts left the DiT modulating the picture as if it were text. +- **Projects remember which model family they are for.** Every project on disk had no family + recorded while the global default said MiniMax H3, so the builder read them as LTXAV and + routed the audio VAE at a node H3's graph never builds. That is the reported "phantom Audio + VAE port": one root cause, four separate bugs, all fixed. +- **A wired `positive_conditioning` satisfies Studio's CLIP requirement**, and an + fl2va/ref2va node satisfies H3's AV latent requirement — neither should have been blocking + generation. +- **Warn only when Studio has no conditioning source at all**, rather than whenever one + particular input is empty. ### Changed - **Guide strengths span the full 0..1 range.** Mid-scene guide, identity pin, prior-scene guide, the per-guide strength field, and the JoyAI memory floor were clamped to 0.25–0.5. That band is the measured audio-safe sweet spot, not a physical limit; it stays in the tooltips and out of the code. +- **Project texts reach nodes that encode on their own**, expanded — so a custom encoder in + the graph sees the same prompt the pipeline does. - The H3 `v2a_grad_scale` warning chip is gone. The knob does nothing without JoyAI audio memory on any model, so flagging it as an H3 limitation blamed the wrong thing. diff --git a/README.md b/README.md index 0c646b0c..5b9ee836 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,13 @@ Use only `main` if you want the most stable version of FunPack. Bug reports base From time to time other short-lived branches may appear for the most advanced yet most breaking changes and features (e.g. a large rework in progress). I do not recommend using any non-`main` branch unless it's strictly necessary for your workflow — any sort of stability is not guaranteed, and such branches can be force-pushed or deleted at any time. -## Version 3.0 — Cutting Room and development direction +## Versions + +Since **3.5.1**, each major version carries a release codename — an adjective and a vegetable +sharing an initial, advancing alphabetically, in the tradition Ubuntu made familiar. All of +3.x is **"Auspicious Asparagus"**. It is shown in Settings ▸ About. + +### Version 3.0 — Cutting Room and development direction FunPack **3.0** introduces the **Cutting Room** — a dedicated montage editor at `/funpack/movie` inside ComfyUI. It is the primary surface for building multi-scene projects, previewing on a real timeline, and driving FunPack Studio + the LTXAV Scene Chain Sampler without wiring a graph by hand. @@ -82,6 +88,22 @@ the model's comfortable window, and a progress readout that says which scene and running. Context windows, which never actually worked before, are fixed — see the [CHANGELOG](CHANGELOG.md) for the full list. +**3.5.0** adds a **second model family**: MiniMax H3 alongside LTX-2 / LTXAV. A project picks a +family, and everything downstream follows from that choice — which nodes the graph emits, which +model files the setup asks for, which sampler settings are even applicable, and what counts as a +valid scene length. Nothing about LTX-2 changes. + +**3.5.1 "Auspicious Asparagus"** is the first release with a codename, and a +compatibility-and-polish pass on top of 3.5.0. **LTX-2.5** works — it reuses the same model +classes behind new config flags, so almost everything binds unchanged, and the two places that +would have broken *silently* are fixed. **Settings ▸ About** now reports the machine ComfyUI +runs on (chip, memory, GPU and VRAM, disk, OS, Python, torch, and which fast-attention backend +is actually installed), which is the thing you want at a glance on a fresh rental. **ALG** runs +on any sampler rather than only FunPack's Distilled Flow, and its two duplicate switches became +one. **`cut_opening_frames`** no longer leaves a noisy first frame — it cuts decoded pixels +instead of latents, and the count is now exact. See the [CHANGELOG](CHANGELOG.md) for the full +list. + See [`docs/MovieEditor.md`](docs/MovieEditor.md) for complete Cutting Room documentation. ## Installation diff --git a/movie_editor/backend/git_update.py b/movie_editor/backend/git_update.py index 616babee..830a82b2 100644 --- a/movie_editor/backend/git_update.py +++ b/movie_editor/backend/git_update.py @@ -39,6 +39,25 @@ def funpack_version() -> str: return m.group(1) if m else "" +# Release codenames, the way Ubuntu does it: an adjective and a vegetable sharing an +# initial, advancing alphabetically. Keyed by MAJOR version, so every 3.x release ships +# under the same name and only a new major earns a new letter. +CODENAMES = { + "3": "Auspicious Asparagus", +} + + +def funpack_codename(version: str = "") -> str: + """Codename for `version` (default: the installed one), or "" when its major has none. + + Kept beside funpack_version because they are read together and shipped together; a + major with no entry simply has no codename yet, which the UI renders as absence rather + than as a blank line. + """ + major = (version or funpack_version()).split(".", 1)[0].strip() + return CODENAMES.get(major, "") + + def _current_branch() -> str: proc = _run_git("rev-parse", "--abbrev-ref", "HEAD") if proc.returncode != 0: @@ -115,6 +134,7 @@ def status() -> dict: return { "ok": True, "version": funpack_version(), + "codename": funpack_codename(), "branch": branch, "commit": commit, "dirty": dirty, @@ -125,7 +145,8 @@ def status() -> dict: "repo": str(REPO_ROOT), } except GitUpdateError as e: - return {"ok": False, "version": funpack_version(), "detail": str(e)} + return {"ok": False, "version": funpack_version(), + "codename": funpack_codename(), "detail": str(e)} def pull(branch: str | None = None) -> dict: diff --git a/movie_editor/frontend/settings_window.js b/movie_editor/frontend/settings_window.js index 2999b117..0ae8f755 100644 --- a/movie_editor/frontend/settings_window.js +++ b/movie_editor/frontend/settings_window.js @@ -246,6 +246,15 @@ const git = G ? G.get() : null; wrap.append(el("div", "sw-about-mark", "◉")); wrap.append(el("div", "sw-about-name", "FunPack")); + // Ubuntu-style release codename, carried on the git status alongside the version. + // Keyed by major, so it reads "3 “Auspicious Asparagus”". Omitted entirely when the + // backend is older than the field or the major has no name yet — an empty quoted + // string would look like a bug. + if (git?.codename) { + const major = String(git.version || "").split(".")[0]; + wrap.append(el("div", "sw-about-codename", + (major ? major + " " : "") + "“" + git.codename + "”")); + } // window.FunPackAppName lets a different frontend sharing this file (e.g. Easy // Gen) relabel the app name without forking the whole section; Editor leaves it // unset and keeps "Cutting Room". diff --git a/movie_editor/frontend/styles.css b/movie_editor/frontend/styles.css index 67dc5e0b..90638130 100644 --- a/movie_editor/frontend/styles.css +++ b/movie_editor/frontend/styles.css @@ -1723,6 +1723,8 @@ body.tour-mode { overflow: hidden; } .sw-about-mark { font-size: 56px; line-height: 1; color: var(--amber); text-shadow: 0 0 24px rgba(243,169,60,.45); margin-bottom: 10px; } .sw-about-name { font-family: var(--font-display); font-weight: 700; font-size: 28px; letter-spacing: -.02em; } +.sw-about-codename { font-family: var(--font-display); font-size: 14px; font-style: italic; + color: var(--amber); letter-spacing: .01em; margin-top: 2px; } .sw-about-sub { font-size: 13px; color: var(--muted); margin-bottom: 18px; } /* Two-column grid rather than centred rows: values now run from short ("3.14") to long ("NVIDIA RTX PRO 6000 Blackwell · 95.5 GB · sm_120"), and only a shared column keeps diff --git a/movie_editor/tests/test_version_codename.py b/movie_editor/tests/test_version_codename.py new file mode 100644 index 00000000..680e1ca4 --- /dev/null +++ b/movie_editor/tests/test_version_codename.py @@ -0,0 +1,55 @@ +"""Version and release codename, as shown in Settings ▸ About. + +The codename is cosmetic, so the only things worth pinning are the ones that would make it +look broken: a major with no name must render as ABSENCE (the panel omits the line), never +as an empty pair of quotes, and the pyproject version must stay parseable — About reads it +through the same helper the update check does. +""" +import pytest + +from movie_editor.backend import git_update + + +def test_installed_version_is_parseable(): + version = git_update.funpack_version() + assert version, "About and the update check both read this" + parts = version.split(".") + assert len(parts) >= 2 and all(p.isdigit() for p in parts[:2]), version + + +def test_current_major_has_a_codename(): + assert git_update.funpack_codename() == "Auspicious Asparagus" + + +def test_codename_is_keyed_by_major_not_by_release(): + """Every 3.x release ships under one name; only a new major earns a new letter.""" + for version in ("3.0.0", "3.5.1", "3.99.12"): + assert git_update.funpack_codename(version) == "Auspicious Asparagus" + + +def test_unknown_major_has_no_codename(): + """Absence, not an empty string in quotes — the panel omits the line entirely.""" + assert git_update.funpack_codename("9.0.0") == "" + assert git_update.funpack_codename("") != None # noqa: E711 - never None, always str + + +@pytest.mark.parametrize("version", ["3", "3.5", "3.5.1", " 3.5.1 "]) +def test_codename_tolerates_short_and_padded_versions(version): + assert git_update.funpack_codename(version) == "Auspicious Asparagus" + + +def test_codenames_advance_alphabetically(): + """The Ubuntu rule this follows: adjective and vegetable share an initial, and the + initial advances with the major. A future entry that breaks it should fail here.""" + for major, name in sorted(git_update.CODENAMES.items()): + adjective, _, noun = name.partition(" ") + assert noun, f"{name!r} should be two words" + assert adjective[0].lower() == noun[0].lower(), f"{name!r} initials differ" + majors = sorted(git_update.CODENAMES, key=int) + initials = [git_update.CODENAMES[m][0].lower() for m in majors] + assert initials == sorted(initials), f"codename initials must ascend: {initials}" + + +def test_status_carries_the_codename(): + """The About panel reads it off the git status payload, not a separate call.""" + assert "codename" in git_update.status() diff --git a/pyproject.toml b/pyproject.toml index fe92ce68..33b0a79a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,9 @@ [project] name = "ComfyUI-FunPack" # Unique identifier for your node. Immutable after creation. description = "An AI movie studio inside ComfyUI: the Cutting Room browser-based video editor with timeline, per-scene prompts and one-click montage generation, plus a self-improving engine that learns from your ratings. Includes the full node stack (Studio, Refiner V2, LTXAV Scene Chain Sampler) for classic graph workflows." -version = "3.5.0" # Custom Node version. Must be semantically versioned. +version = "3.5.1" # Custom Node version. Must be semantically versioned. +# Release codename ("Auspicious Asparagus" for all of 3.x) lives in +# movie_editor/backend/git_update.py:CODENAMES — keyed by major, shown in Settings ▸ About. license = { text = "GNU GPL v3" } dependencies = [] # Filled in from requirements.txt