Commit Graph
5028 Commits
Author SHA1 Message Date
Miguel Ángel d49e527b32 fix(engine): ffmpeg steps and downloads stop on no progress, not while still advancing (#6258)
* fix(engine): ffmpeg steps and downloads stop on no progress, not while still advancing

* test(cli): an ffmpeg step that stops advancing reports inactivity, not a deadline
2026-10-25 23:04:13 +00:00
Abdullah M. Nazif 693447f670 fix(engine): keep capture diagnostics on stderr (#5304)
* fix(engine): keep capture diagnostics on stderr

* chore: prepare conflict resolution against current main

* chore: preserve reviewed fix on current main

* fix(engine): send motion blur diagnostics to stderr

* chore: prepare reviewed capture conflict resolution

* fix(engine): preserve stderr diagnostics across capture updates

* chore: prepare reviewed capture conflict resolution

* fix(engine): preserve stderr for scene callback warnings
2026-10-25 23:04:09 +00:00
Miguel Ángel 2db0c56bfc fix(producer): keep the page background opaque when motion blur renders an MP4 (#6262)
* fix(producer): keep the page background opaque when motion blur renders an MP4

* fix(engine): drop the field comment
2026-10-25 23:03:24 +00:00
Akshar Patel 64c7bbf25d fix(sdk): restore saved edits for dotted element IDs (#6287)
* fix(sdk): restore saved edits for dotted element IDs

* fix(sdk): skip unknown saved override values
2026-10-25 23:01:41 +00:00
Akshar Patel 04df17cccb fix(sdk): keep font and image declarations after object edits (#6281)
* fix(sdk): preserve font and image declarations after object edits

* fix(studio): preserve variable object defaults when editing declarations

* fix(studio): show font and image values in bind warnings
2026-10-25 23:01:32 +00:00
Abdullah M. Nazif 6c053bc8e7 docs(producer): clarify ProRes capture precision (#6244)
* docs(producer): clarify ProRes capture precision

* docs(producer): clarify captured alpha precision
2026-10-25 23:01:31 +00:00
Abdullah M. Nazif c194969242 fix(skills): load dotLottie through its browser ESM entry (#5562)
* fix(skills): load dotLottie through its browser ESM entry

* chore: prepare conflict resolution against current main

* fix(skills): retain local Lottie setup after syncing main

* chore: prepare conflict resolution against current main

* fix(skills): retain browser dotlottie setup in current runtime guidance

* chore: prepare conflict resolution against current main

* fix(skills): refresh dotLottie fingerprints against current main

* chore: prepare conflict resolution against current main

* chore: merge main and refresh dotLottie skill manifest

* chore: prepare conflict resolution against current main

* fix(skills): keep Lottie setup guidance in its reference
2026-10-25 23:01:29 +00:00
Akshar Patel 6a20ec9e9a fix(cli): preserve clip lengths when duplicating media (#6280)
* fix(cli): prevent overlaps when duplicating natural-duration media

* fix(cli): preserve absolute ends when duplicating clips

* fix(cli): keep rippled clip bounds together
2026-10-25 23:01:27 +00:00
Abdullah M. Nazif 707c150cbd fix(cli): include external srcset images in project archives (#6335) 2026-10-25 22:55:35 +00:00
Abdullah M. Nazif abb06b47b7 fix(engine): verify WebGPU device creation before capture (#6343) 2026-10-25 22:54:30 +00:00
Miguel Ángel 553f2c5880 fix(core): a timeline set that hides a timed clip hides it in preview and render (#6351)
* fix(core): a timeline set that hides a timed clip hides it in preview and render

* fix(core): tell a script's hidden clip from the runtime's by the declaration's priority

* fix(core): a scene a script hides takes its timed children with it
2026-10-25 22:53:10 +00:00
Abdullah M. Nazif f2aa62beb8 fix(producer): reject regression audio stream mismatches (#5228)
* fix(producer): reject regression audio stream mismatches

* chore: prepare conflict resolution against current main

* chore: preserve reviewed fix on current main

* chore: prepare conflict resolution against current main

* fix(producer): retain audio regression checks with current main

* chore: prepare conflict resolution against current main

* fix(producer): preserve audio presence checks on current main
2026-10-25 22:52:31 +00:00
Abdullah M. NazifandMiguel Angel Simon Sierra 3aca8060b8 fix(cli): preserve distinct Lottie animations during capture (#5293)
Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
2026-10-25 22:46:44 +00:00
Miguel Ángel 0ae48c9f33 fix(core): a root without data-duration stays on screen after its GSAP timeline ends (#6354)
A root composition with no data-duration took its own GSAP timeline's length as its visibility
window, so when media made the composition longer, the whole frame went black from the end of
that timeline in preview and render. The root's window is now open-ended, bounded by the
composition's length. In a framed preview, the duration the runtime stamps on GSAP targets for
Studio's timeline no longer hides the clips inside them either: the ancestor chain check now
skips discovery stamps, as the clip's own visibility pass already does.
2026-10-25 22:12:42 +00:00
Miguel Ángel 33685ef2be fix(core): a self-closed audio group no longer hides the clips after it (#6344)
* fix(core): a self-closed audio group no longer hides the clips after it

HTML does not close a custom element on `/>`, so `<hf-audio-group id="sfx" />` takes in every
later sibling, and the runtime's inert style (`display:none`) hid them in preview, snapshot and
render. The style is now `display:contents`: an empty group still makes no box, and anything
inside one renders as written. A lint warning names the elements a group holds, and the docs and
mixing reference show the group closed.

* style(core): cut the audio group comments to the comment length limits

* fix(core): a muted audio group still hides what sits inside it

A muted bus (`data-hidden`) is hidden by an inline display:none that the bus rule outranked, so
clips inside a muted self-closed bus kept painting while their audio and video frames dropped.
2026-10-25 21:44:32 +00:00
Miguel Ángel 059a6e55d4 fix(producer): never probe a hidden video clip (#6340) 2026-10-25 20:51:08 +00:00
Miguel Ángel 2c10eb0a33 chore: release v0.8.200 (#6337) 2026-10-25 20:50:21 +00:00
Miguel Ángel a6121faa72 fix(core): a scene callback that throws no longer stops the main timeline in render (#6322)
* fix(core): a scene callback that throws no longer stops the main timeline in render

Render seeks now leave each scene paused in the root's pass and seek it on its own clock first,
as preview does, so a throwing onUpdate cannot cut the root's tweens short. The error is logged
once per scene as a composition script error, and the render log shows it once per render.

* fix(core): a scene registered without a timeline is not reported as a script error

A placeholder registration ({}) has nothing to seek. seekGsapTimeline now only calls seek()
when the object has one, as player.ts already does, so the new scene-error report stays for
real callback errors.

* fix(core): report a scene callback error as a warning, keeping render failure classes

Logged as a composition script error, a scene callback that threw became a page error, which
turns a timed-out timeline wait into a script failure and cuts the wait short. It is now a
runtime warning the engine forwards to the render log once per render, outside page errors.

* chore(core): regenerate the position-edits render bundle for the new diagnostics export
2026-10-25 20:48:03 +00:00
Miguel Ángel 0c169e2c56 perf(producer): fast capture stays on for frames a blend mode does not touch (#6330) 2026-10-25 20:24:49 +00:00
Miguel Ángel c163330a16 fix(engine): the drawElement self-check reads video comps' truth on a separate page too (#6321)
* fix(engine): the drawElement self-check reads video comps' truth on a separate page too

The video-frame injector now hands out a fresh hook with state of its own, so the check can paint the frames on its disposable page without the render page's injector losing track of what it has painted.

* fix(engine): the truth page gets the media render-id bridge the render page has

Videos that share a plain id (a bare video in each of two scenes) are told apart by a render id the bridge resolves; without it the truth page painted the wrong element.
2026-10-25 19:57:09 +00:00
Miguel Ángel d7efd809f6 feat(producer): record video frame extraction time in the chunk perf sidecar (#6329)
* feat(producer): record video frame extraction time in the chunk perf sidecar

* docs(producer): say videoExtractMs overlaps capture, not the boot
2026-10-25 19:52:49 +00:00
Miguel Ángel 70187a13b5 perf(core): the preview mounts and draws only the scenes near the playhead (#6189)
* perf(core): skip rendering preview scenes far from the playhead

* refactor(core): split the far-scene pass into small helpers

* perf(core): a first render reveals only the clips it reaches

* fix(core): reveal the clips holding first-rendered tweens, wherever their window is

* feat(core): a scene may load a project helper and wait for fonts and still swap

* perf(core): the preview mounts only the scenes near the playhead

* perf(core): a seek waits only for the scene it lands in; neighbours mount one per turn

* feat(studio): selecting a clip in an unmounted scene mounts that scene first

* fix(core): a source a play and a seek both need is fetched and decoded once

* fix(core): a scene mounting during play joins the mix without restarting what plays

* fix(studio): selecting a clip waits on a scene mount only in a preview that unmounts scenes

* fix(core): far scenes still mount after an edit, and an edit to a parked scene keeps its styles

* perf(core): web audio schedules a clip only as the playhead comes near it

* chore(studio): trim the scene hold helper's comments to its package's share

* fix(core): a scene the preview unmounts is freed, not kept by audio, caches or old roots

* fix(studio): an unmounted scene's rows and their state come from the markup it mounts with

* perf(core): the preview mounts scenes ahead of play and keeps recent ones mounted

* refactor(core): keep the far-scene neighbour rule private to its module

* style(core): format the far-scene neighbour rule

* perf(core): the preview unmounts scenes the playhead leaves, as kept ones slowed each mount

* fix(core): scenes that measure text, call their own animate or wait on font loads can unmount

* fix(core): each preview scene carries the helpers it loads, so it mounts on its own

* fix(core): a mounted scene shows though its stand-in was hidden by display

* fix(core): a helper scenes share runs once for the film, and a remounted scene keeps showing

* fix(core): a scene that also publishes its timeline under another id stays mounted

* feat(core): scenes that draw in 2D or chain the timeline they register can unmount

* perf(core): a play frame that draws nothing new skips the hidden-clip lookup

* fix(core): a GSAP file copied into a project counts as the library for scene unmounting

* style(core): drop an unneeded escape in the registered-chain pattern

* chore: core and producer size budgets make room for the preview scene window

* fix(core): a scene that draws on a 2D canvas stays mounted

* fix(core): a scene the preview unmounts lets go of timelines it built after a font wait

* perf(core): a scene leaving the playhead keeps its last rendered size, no forced layout at the cut

* refactor(core): the far-scene skip keeps last-render boxes in their own helper

* fix(core): a mounting scene never records the runtime's root timeline as its own

* chore(producer): raise the size budget to the measured size with the preview runtime inlined

* docs(core): keep the far-scene and scene-memory comments within the comment limits

* perf(core): style each hidden clip visible once ahead of its cut, so the cut frame does not

* fix(core): a seek into a scene still mounting as a neighbour waits for that mount

* chore(core): keep comment share and size budgets to main plus the measured core growth
2026-10-25 19:39:14 +00:00
Miguel Ángel c50918277a perf(engine): fast capture stays on for frames a shadow, blur or clip effect does not touch (#6318)
* perf(engine): fast capture stays on for frames a shadow, blur or clip effect does not touch

A single drop-shadow, blur, backdrop-filter or blend element, or one clip-path or mask
tween, sent the whole render to screenshot capture, 2-3x slower than drawElement. The two
init gates now name the frames the effect can show on. Only those frames capture by
screenshot, in a second browser opened on the first of them; every other frame keeps
drawElement.

* fix(engine): scan effects in a throwaway session; hand risk frames over only where captured

The effect scan seeked the render's own page to the end, leaving its timeline state ahead of
the render. It now runs in a throwaway session with the render's own seek. A hidden effect
element with a visible descendant counts as shown. Per-frame capture is opt-in per caller
(deFrameFallback), so disk, segmented, chunk and parallel-worker sessions keep the
whole-render fallback, and a route that cannot hand frames over drops drawElement up front.

* fix(engine): a timeline callback keeps the whole-render fallback; trim comment share

A tl.call or onComplete can add an effect's class to any element mid-render, so the elements
found at the start are not the only ones it can reach. Such a timeline is unbounded and keeps
the whole-render screenshot fallback.

* style(engine): shorten the live-frame scan comment

* perf(engine): after a timeline callback, not before, every frame may show the effect

A callback can add an effect's class to any element, so only the frames from the first
callback on lose their element bounds. Frames before it keep the scanned bounds, so a late
callback no longer sends the whole render to screenshot capture.

* perf(engine): open the second screenshot browser only when memory is available for it

Per-frame capture needs a second browser (479 MB measured at 1080p). When the render would
leave less than 1.5 GB available after it, the render keeps the whole-render screenshot
fallback instead. getAvailableMemoryMb moves from the CLI to the engine's system-memory
module, which already owns the host's memory readings; os.freemem() reads 367 MB on this
49 GB Mac, so the guard uses free, inactive, speculative and purgeable pages.

* fix(engine): budget the whole 1 GB per-frame capture adds, not just the second browser

The render's process tree goes from 1002 to 2001 MB at 1080p: the screenshot browser (479 MB)
plus a drawElement browser that is itself about 500 MB larger than a screenshot one. The guard
budgeted 500 MB. It now budgets the 1 GB measured, scaled by output pixels.
2026-10-25 19:15:18 +00:00
Miguel Ángel 69405a7736 perf(producer): size a chunk's capture browsers by its frame cost, not its frame count (#6319) 2026-10-25 19:14:30 +00:00
Miguel Ángel 1a37db4aee fix(producer): the audio peak correction sizes its cut from the PCM it re-encodes (#6312) 2026-10-25 18:53:14 +00:00
Miguel Ángel 42b84632a8 chore: release v0.8.199 (#6328) 2026-10-25 18:13:20 +00:00
Miguel Ángel b2124b2a56 perf(studio-server): the browser-playable copy of an alpha video encodes on four threads (#6325)
* perf(studio-server): the browser-playable copy of an alpha video encodes on four threads

libvpx runs on one thread unless told, so the alpha copy of a 4K video took several times its length on a small machine.

* docs(studio-server): drop a comment line the name already carries
2026-10-25 17:35:33 +00:00
Miguel Ángel 21f315e9cc fix(engine): the drawElement self-check reads its truth on a page the render does not use (#6316)
* fix(engine): the drawElement self-check no longer rejects count-up text it should accept

The check's ground-truth screenshot was taken after a seek that suppressed timeline callbacks, so text a callback writes (a count-up) was stale in the truth while the real render, which runs callbacks, showed it correctly. The check failed and the whole render restarted on the slower screenshot path.

* docs(engine): shorten a comment to the line limit

* fix(engine): the drawElement self-check reads its truth on a page the render does not use

Callbacks now run in the truth seek (a count-up or class swap no longer looks stale), and they run on a disposable page so a callback that changes the page cumulatively cannot change what the render draws. Compositions with videos keep the in-page path, because the video injector is tied to the render page.

* docs(engine): cut a comment to three lines

* fix(engine): a truth page that cannot open falls back to the render page
2026-10-25 17:34:41 +00:00
Miguel Ángel 7f5d6ed203 feat(producer): fail a render that cannot finish within its maxRenderMs budget (#6224)
* feat(producer): fail a render that cannot finish within its maxRenderMs budget

* chore(producer): raise the producer size budget to the measured size of the render time guard
2026-10-25 17:31:18 +00:00
Miguel Ángel 15b00007ab test(engine): tone-map concurrency test passes on macOS (#6323)
On macOS the extractor checks once per process whether ffmpeg has zscale. The test ran that
check through its counting shim, so it saw 11 ffmpeg runs instead of 8 cuts plus 2 probes and
failed on every Mac with zscale. Take the check with the real ffmpeg before the shim counts.
2026-10-25 17:14:41 +00:00
Miguel Ángel d4812a0393 test(cli): check the browser that ran the hanging script, and that every browser it started is gone (#6320)
* test(cli): check the browser that ran the hanging script, and that every browser it started is gone

* test(cli): type the launch recorder's pid file and drop the added comment
2026-10-25 16:52:39 +00:00
Miguel Ángel 26e3045e3b fix(producer): --workers N is a ceiling, drawElement renders keep their single worker (#6314)
* fix(producer): --workers N is a ceiling, drawElement renders keep their single worker

* docs(producer): keep the inversion predicate's doc as short as before
2026-10-25 15:33:05 +00:00
Miguel Ángel ad6771c435 feat(lint): warn when a scene's styles sit outside its template (#6317)
A sub-composition file with a <template> is mounted from the template alone, so a <style>
in its <head> (or a stylesheet link in its body) never applies in preview, snapshot or render.
Warn on it, with the fix in the message: move it inside <template>.
2026-10-25 15:26:20 +00:00
Miguel Ángel 7b76377534 test(engine): count what the mixer read from a cloud file, not what the server buffered (#6315) 2026-10-25 15:25:34 +00:00
Miguel Ángel d3a7599c60 fix(cli): a failed frame check no longer switches parallel capture off for good (#6313)
* fix(cli): a failed frame check no longer switches parallel capture off for good

The per-install breaker tripped on the first self-verify fallback and nothing ever cleared it. Now a second fallback within 20 router renders turns the router off, a new CLI version or 20 clean renders turn it back on, and the stored record keeps the rejected frame (reason, frame, dB, threshold, version, time).

* docs(cli): shorten three comments to the line limits

* refactor(cli): keep the breaker record parsing small and the comments within the ratchet
2026-10-25 15:24:47 +00:00
Miguel Ángel 7569ec1be5 fix(producer): extract HDR video frames a window at a time to bound scratch disk (#6304)
* fix(producer): extract HDR video frames a window at a time so scratch disk stays bounded

* fix(producer): drop the added compositor comment for the Comments gate

* fix(producer): keep the HDR scratch read plain and fail the render when a window cannot be extracted

* fix(producer): settle a looped HDR source's frame count before capture starts

* chore(producer): raise the packed size budget by the windowed HDR extraction's measured delta
2026-10-25 15:13:31 +00:00
Miguel Ángel 9763b68a33 fix(studio): scrubbing never auditions a track the music row did not name (#6310)
The last fallback took whatever audio came first in the preview, which is usually the voiceover, whenever the music row's own element was momentarily absent.
2026-10-25 14:52:41 +00:00
Miguel Ángel 80ba3f59ec fix(core): the decoded-audio cache keys a clip the same way everywhere (#6306)
* fix(core): the preview keeps a voiceover's decoded audio across an in-place reload

The decode cache and the release pass keyed the same asset two ways, so a clip the release pass was handed as live could lose its buffer and be re-fetched mid-playback.

* refactor(core): resolve the decode key through the runtime's own assetUrl

Smaller than the inline URL resolution it replaces, which tipped the producer package-size budget, and one owner for "resolve against the document".

* refactor(core): drop the decode-key comment the comment ratchet rejects

* fix(core): an unreadable clip src no longer stops the preview from playing

Resolving the decode key threw on a src that is not a URL, out of the release pass and through play(), so one bad clip silenced the whole preview instead of just failing its own fetch.
2026-10-25 14:52:35 +00:00
Miguel Ángel ada9932d4e fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled (#6307)
* fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled

* perf(core): a video copy's size box follows the stage within a fifth, not a half

* docs(cli): shorten two comments to the line limit

* fix(cli): the copy heartbeat drops a finished copy; size budgets follow the stall and progress code

* chore: size budgets rise by this change's measured bytes
2026-10-25 13:53:03 +00:00
Miguel Ángel 89af290808 perf(core): mounting a scene no longer restyles it to look for raster hints (#6300)
* perf(core): mounting a scene no longer restyles it to look for raster hints

* perf(core): raster hints read inline hints directly instead of matching every style

* perf(core): raster hints read the stylesheets once per page check, not per batch

* perf(core): raster hints watch no attributes while nothing is marked or hinted

* fix(core): a copied or re-attached marked layer keeps being followed

* chore(producer): raise the packed size budget by the 336 bytes the raster-hint fix adds

The producer bundles the runtime. On main (aae32e43ed) it packs at 773560 bytes
(2821768 unpacked) and this branch at 773896 (2822665): packed rises by 336, unpacked by 897.
2026-10-25 12:52:45 +00:00
Miguel Ángel d7f323d319 chore: release v0.8.198 (#6305) 2026-10-25 12:42:50 +00:00
Miguel Ángel aae32e43ed perf(producer): cut distributed chunks by estimated capture cost (#6295)
* perf(producer): cut distributed chunks by estimated capture cost, same chunk count

Auto-sized plans place chunk boundaries so each chunk carries about the same estimated capture
cost (page base plus active video megapixels); chunk count, explicit chunkSize, uniform-cost
compositions and the targetChunkFrames ceiling are unchanged. Frame-cost calibration samples by
cost interval plus the heaviest frame, so a heavy window is always measured.

* fix(producer): hold cost-weighted chunks to the worker time limit's frame cap

The weighted cut could grow a chunk up to MAX_CHUNK_SIZE frames, past the per-chunk frame count resolveMaxParallelChunks sized against the worker time limit. The cut now takes the same cap, from one shared helper, and keeps the equal-frame size as its floor.

* fix(producer): keep the chunk-cut doc comment under 120 characters

* perf(producer): drop the calibration sampler change from the cost-weighted cut

The cut no longer touches how capture cost is measured; that keeps the producer bundle within its size budget and the change to the chunk boundaries alone.

* perf(producer): compute the chunk frame cap inline to keep the bundle small
2026-10-25 11:25:00 +00:00
Miguel Ángel 6b424e02c4 fix(cli): snapshot waits for a slow scene to finish building instead of saving a stale frame (#6290)
* fix(cli): snapshot waits for a slow scene to finish building instead of saving a stale frame

* fix(cli): drop the added comments, the names say it

* test(cli): give the contact-sheet engine mock the render-ready timeout snapshot reads
2026-10-25 10:59:47 +00:00
Miguel Ángel 36b2c3c2b9 fix(core): hide static data-hidden elements in render as preview does (#6296)
* fix(core): hide a static data-hidden element in render as preview does

* fix(core): name the static-hidden skip instead of commenting it
2026-10-25 10:33:10 +00:00
Miguel Ángel 683e9b1765 fix(producer): refuse private and non-HTTPS previewUrl fetches (#6297) 2026-10-25 09:50:18 +00:00
Miguel Ángel f51650d8a7 chore: release v0.8.197 (#6298) 2026-10-25 09:17:12 +00:00
Miguel Ángel 382a14d73e perf(engine): vp9 and vp8 sources without an alpha plane extract as jpeg (#6284)
A VP8/VP9 source with no alpha tag has one frame decoded with the alpha-aware decoder when its
metadata is read; if that frame has no alpha plane, its frames extract as jpeg instead of png.
Tagged alpha, a decoded alpha plane, a failed probe, or a plan written without the field keep png.
2026-10-25 08:32:49 +00:00
Miguel Ángel 495dc95acd fix(sdk): release the parsed film when a session is disposed (#6289)
* fix(sdk): release the parsed film when a session is disposed

A disposed session kept its whole parsed document and element caches. Anything
that still referenced the session, such as a stale closure or React fiber,
therefore kept the replaced film in memory, so memory grew with every film swap.
dispose() now drops the document and caches.

* fix(sdk): warn when a disposed session is read

A disposed session answers reads from an empty document. Outside production it
now logs once, with a stack, so a caller still holding a stale session shows up
instead of silently reading nothing.
2026-10-25 07:34:26 +00:00
Miguel Ángel 2bff65b0cd feat(lint): warn when a round-capped line hidden by its dash settings can still show a dot (#6291)
* feat(lint): warn when a round-capped line hidden by its dash settings can still show a dot

* docs(lint): shorten the round-cap dash rule entry
2026-10-25 07:25:17 +00:00
Miguel Ángel 4d154c6a23 test: two flaky queue tests no longer race real time or the first copy's start (#6283) 2026-10-25 07:13:22 +00:00