Commit Graph
1183 Commits
Author SHA1 Message Date
Miguel Ángel 85fdc9aa88 chore: release v0.8.206 (#6469) 2026-10-26 15:00:57 -04:00
Miguel Ángel e3e93aa7fe fix(core): paused preview and render show the same, correct video frame (#6432)
* feat(core): one function gives the media time that shows the frame starting at an instant

* fix(core): paused preview and render show the same, correct video frame

* fix(core): register the media-clock subpath in its descriptor and keep the reason beside the test

* fix(core): media seeks lead by 10 us so Chrome's timestamp rounding never shows the prior frame

* fix(core): generated pages lead media seeks by a literal, on every generator branch
2026-10-26 17:48:51 +00:00
Miguel Ángel 4a5ee73614 fix(producer): a composition with no timeline-registering script no longer waits 45 s at setup (#6436)
* fix(producer): a composition with no timeline-registering script no longer waits 45 s at setup

The render waited the full timeout for every composition that never registers a timeline, such as one driven by canvas or requestAnimationFrame.

* fix(producer): module and importing scripts still count as able to register a timeline

* fix(producer): a failed script keeps the readiness wait, and the page scan runs once
2026-10-26 17:28:56 +00:00
Miguel Ángel 5148326fd6 fix(cli): a killed CLI no longer leaves its child processes running (#6431)
* fix(cli): a killed CLI no longer leaves its child processes running

* test(cli): shorten the reaper fixture comment to fit the comment ratchet
2026-10-26 17:28:50 +00:00
Miguel Ángel 37f14fdcb1 fix(snapshot): video frames keep the colours render gives the same footage (#6425)
* fix(snapshot): video frames keep the colours render gives the same footage

* fix(snapshot): say so once when video colours cannot be matched to render

* test(snapshot): cut the HEVC fixture's encode time so the flake gate stays inside its timeout

* style(snapshot): format the HEVC fixture line
2026-10-26 17:27:13 +00:00
Miguel Ángel ad4d3fad13 fix(producer): convert HEIC images to JPEG at compile time (#6455)
* fix(producer): convert HEIC images to JPEG at compile time

Tiled HEIC photos probe as several video streams, so the media-type
preflight rejected them as image:video, and Chrome cannot decode HEIC.
Sniff the ftyp brands of every <img>, join and rotate the tile grid with
ffmpeg, verify the declared size, and swap in a cached JPEG.

* fix(producer): shorten the HEIC prep comments

* fix(producer): name host tool failures and keep a failed HEIC preview image as authored

Also: HEIC tests run in the integration lane; the primary grid is picked by default disposition.

* chore(producer): raise the package size budget for the HEIC prep
2026-10-26 13:56:13 -04:00
Miguel Ángel 83a29e9209 fix(engine): name the element a failing root composition script looked up (#6430)
* fix(engine): name where a root composition script threw and the element it looked up

* style(engine): format throw-site helper

* fix(engine): shorten the throw-site comment

* fix(engine): name a lookup only when the throwing line holds exactly one
2026-10-26 16:15:42 +00:00
Miguel Angel Simon Sierra b4eab1a8c1 chore: release v0.8.205 2026-10-26 12:41:40 -04:00
Miguel Ángel d8398c9da7 fix(engine): a video held past its media end keeps following its animation (#6424)
* fix(engine): a video held past its media end keeps following its animation

Fixes #4049.

* fix(engine): drop two comments the names already carry
2026-10-26 16:12:30 +00:00
Miguel Ángel 2d1b5cfbd2 chore: release v0.8.204 (#6423) 2026-10-26 10:16:23 -04:00
Miguel Ángel f16c23cd99 fix(engine): a slow first Chrome start no longer drops a render to screenshot capture (#6420)
The beginFrame probe now tells a timeout from a real rejection.

A timeout relaunches Chrome and probes once more with a longer budget.

An unsupported answer still falls back at once.
2026-10-26 13:49:57 +00:00
Miguel Ángel 575f68cf5b fix(producer): a render whose capture stops advancing fails within minutes (#6413)
* fix(producer): a render whose capture stops advancing fails within minutes

* fix(producer): keep the bounded probe read the stall change dropped

* chore(producer): shorten a test comment

* fix(producer): every retry batch gets its own first-frame window; the stall test waits for the render to settle

A retry that runs as several batches opened one stall window for the whole attempt, so a later batch whose browsers take longer than the no-progress window to land a frame was killed where main would have finished. The window now opens per batch. The stall tests also waited a fixed number of turns for real file IO after the last frame, which failed about 3 runs in 10 on a loaded machine; they now wait for the render to settle.

* fix(producer): a test reads the stall error fields instead of suppressions; size budget follows

* test(producer): split the scripted capture attempt into small helpers

* test(producer): the stall test advances fake time only while the scripted capture waits
2026-10-26 12:20:19 +00:00
Miguel Ángel ff0a56340b chore: release v0.8.203 (#6412) 2026-10-26 07:20:07 -04:00
Vance Ingalls f8d51f3146 chore: release v0.8.202 (#6404) 2026-10-26 07:55:04 +00:00
Miguel Ángel 3ff8cd8683 chore: release v0.8.201 (#6377) 2026-10-26 04:50:26 +00:00
Miguel Ángel e51ec11caa perf(engine): on a Mac, video frames for a render decode on the media engine (#5878)
Frame extraction decoded every source in software, so a long 4K60 HEVC clip
spent much of its CPU on decoding. On macOS, extraction now asks ffmpeg for
VideoToolbox when this ffmpeg has it and the source cannot carry alpha; ffmpeg
falls back to software for a stream VideoToolbox cannot decode, and the frames
written are byte-identical.
2026-10-26 01:33:24 +00:00
Miguel Ángel 924da50637 perf(producer): chunks capture frames round-robin so the encoder runs during capture (#6364)
* perf(producer): chunks capture frames round-robin so the encoder runs during capture

The overlapped encoder takes frames in order, but workers captured contiguous ranges, so it sat idle until worker 0 finished and then encoded the other workers' frames after capture. With the encoder on, workers now take frames round-robin and it keeps pace.

* chore(producer): drop a doc comment to keep the comment ratchet flat

* fix(producer): interleaved capture retries a dead worker as one stride task and keeps static-frame reuse

A worker that dies during round-robin capture leaves every Nth frame missing; the missing-frame retry turned each into its own one-frame batch. It now retries one stride task per dead worker. Screenshot-mode static reuse also required the previous frame to be exactly one back, which never holds at stride above 1; it now reuses when every frame in the gap is static, like the worker path.
2026-10-26 01:32:12 +00:00
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
Abdullah M. Nazif abb06b47b7 fix(engine): verify WebGPU device creation before capture (#6343) 2026-10-25 22:54:30 +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 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 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 42b84632a8 chore: release v0.8.199 (#6328) 2026-10-25 18:13:20 +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 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 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 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 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 45167743a6 chore: release v0.8.196 (#6288) 2026-10-25 06:52:05 +00:00
Miguel Ángel 1776b42736 chore: release v0.8.195 (#6273) 2026-10-25 03:18:09 +00:00
Diogo ChavesandClaude Opus 5.5 eed6ab3b52 fix(engine): software renders reach SwiftShader when VK_DRIVER_FILES pins a GPU (#5976)
* fix(engine): software renders reach SwiftShader when VK_DRIVER_FILES pins a GPU

ANGLE (and Dawn) load SwiftShader by pointing the Vulkan loader at its ICD
through VK_ICD_FILENAMES. The loader ignores VK_ICD_FILENAMES while
VK_DRIVER_FILES is set, and still applies VK_LOADER_DRIVERS_SELECT/DISABLE,
so on a machine that pins its Vulkan driver that way a --no-browser-gpu
render gets ANGLE on the real GPU and assertSwiftShader refuses it.

Launch SwiftShader browsers without those three variables. Hardware
launches, and environments that set none of them, are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(engine): raise the packed size budget by the 342 bytes launchEnv adds

The increase sits on top of main's engine budget, 456055 after #6194.
Main packs the engine at 435883 bytes and this branch at 436205.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 03:00:14 +00:00
Miguel Ángel 2e397dba59 chore: release v0.8.194 (#6269) 2026-10-24 22:17:19 -04:00
Akshar Patel 17332cf0df fix(engine): concat resolves relative paths without dropping clips (#6238)
* fix(engine): concat resolves relative paths without dropping clips

* test(engine): terminate ffprobe options in concat regression
2026-10-25 00:29:23 +00:00
Akshar Patel 066bc51831 fix(producer): keep runtime media speed in exports (#5528)
* fix(producer): preserve runtime media playback rates

* fix(producer): preserve runtime-inserted speed ramps

* fix(producer): retain scene trims for discovered speed ramps
2026-10-25 00:25:04 +00:00
Jay Hemnani c2a6b15f6f fix(core): resolve a relative data-start inside the clip's own composition (#5336)
* fix(core): resolve a relative data-start inside the clip's own composition

* fix(engine): render-side data-start references resolve in the clip's own composition

The engine's reference resolver still looked an id up document-wide, so
with two different compositions sharing an id the producer placed media
from the first one while the runtime used the clip's own. Scope it the
same way as the runtime's startResolver, with the same document-wide
fallback, and correct the SDK comments that called the runtime lookup global.

* fix(parsers): keep getAttribute-only reference adapters working

The scoped lookup needs parentElement, closest and querySelectorAll, but
RefResolverEl is exported from @hyperframes/parsers and @hyperframes/engine.
Make them optional and fall back to the document-wide lookup when an adapter
lacks them.

* refactor: look up a scoped reference with one id query
2026-10-25 00:23:39 +00:00
James RussoandClaude Opus 5.5 f180ffca64 fix(cli): reap only orphaned browsers HyperFrames launched (#6251)
* fix(cli): reap only orphaned browsers HyperFrames launched

Orphan cleanup on preview and render killed every PPID-1 process of the
current user whose args named chrome-headless-shell or a Puppeteer temp
profile, which included detached browsers started by other tools.

Every HyperFrames Chrome launch now passes an inert
--hyperframes-owned-browser switch, and the reaper only kills PPID-1
processes that carry it as a whole argument.

Fixes #6203

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): serve the browser marker from its own subpath

The GCP image's BeginFrame contract step imports the engine's
browserManager straight from source before core's generated runtime
exists, so pulling the marker from the core root entry broke it.
A dependency-free @hyperframes/core/browser-marker subpath keeps that
import chain as light as it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(cli): expect the browser marker in the capture fallback launches

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(cli): say orphan cleanup runs before preview and render

The cleanup only stops browsers HyperFrames launched, so the opt-out is
no longer needed to protect other tools' browsers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 23:09:57 +00:00
Miguel Ángel ab57d0ee14 test(engine): retry the alpha probe test's temp-dir cleanup when Windows holds a file (#6234) 2026-10-24 19:12:51 +00:00
Miguel Ángel 89081b9197 chore: release v0.8.193 (#6229) 2026-10-24 12:39:08 -04:00
Miguel Ángel 3d45d1b037 fix(engine): audio encodes on FFmpeg 4.4, which has no setts duration option (#6219)
* fix(engine): audio encodes on FFmpeg 4.4, which has no setts duration option

The segmented AAC encode ended its stream with setts=duration, added in FFmpeg 5.0, so every render
with audio failed on Ubuntu 22.04's FFmpeg 4.4. An FFmpeg without the option now encodes the audio in
one pass, which ends the stream at the duration itself.

* fix(engine): keep the setts capability check private to the AAC encoder
2026-10-24 16:10:44 +00:00
Miguel Ángel da4386f0d0 perf(aws-lambda): cloud renders plan without reading video bytes and start chunks sooner (#6194)
* perf(engine): a remote source's tail decodes run many at a time, not one per half-core

* perf(producer): the plan logs where its extract stage spends its time

* perf(engine): a window that ends inside its source takes its frame count without a decode

* test(engine): pin the tail-decode slot test to the windows that decode

* style(engine): format the tail-probe window check

* test(engine): deferred counts match extraction on a B-frame NTSC source; VFR is not deferred

* style(engine): format the tail-probe tests

* perf(aws-lambda): a site stores each video's ffprobe JSON, and the plan reads it instead of probing

* test(aws-lambda): a site stores and hands out each video's probe

* style(aws-lambda): format the probe-facts upload and tests

* perf(engine): a site's stored probe also holds the final-frame timestamp the plan would probe for

* test(engine): drop an unused import

* test(aws-lambda): the stored probe holds the ffprobe JSON and the final frame

* test(aws-lambda): the stored probe holds the ffprobe JSON and the final frame

* perf(producer): a distributed chunk encodes while it captures

* perf(producer): the audio task logs where its mix spends its time

One line per audio stage: prepare, groups, mix, master, encode and the peak check, in ms.

* perf(engine): locked warmup ticks stop sleeping once the page is ready

* perf(engine): a window reaching a stored video's end takes its frame count from stored facts

* fix(producer): the cloud audio task logs its phases

* perf(engine): stored facts keep the last packet length so late-start windows skip the tail decode

* fix(engine): a probe spawns without waiting when no stored facts are declared

* chore(aws-lambda): shorten comments over the line limit

* fix(engine): warmup keeps pacing after a failed tick; stored final frames check ffprobe major

* test(engine): the recorded ffprobe major matches the build's own banner

* test(engine): the version-banner probes go through a named binary like the other spawns
2026-10-24 14:30:16 +00:00
serdarucelandserdarucel 3dc0b97bfd fix(engine): disable Dawn's DXC on Windows so WebGPU requestDevice succeeds (#5830)
The chrome-headless-shell that HyperFrames downloads on Windows ships
without dxil.dll and dxcompiler.dll. Dawn's D3D12 backend loads them in
requestDevice, so the adapter is found but device creation fails and
three.js WebGPURenderer silently falls back to WebGL2.

Pass --disable-dawn-features=use_dxc next to --enable-unsafe-webgpu on
win32.

Refs #5775

Co-authored-by: serdarucel <196021214+serdarucel@users.noreply.github.com>
2026-10-24 11:16:01 +00:00
Miguel Ángel 1ab871fb11 fix(producer): a render's page-error count includes what the capture workers saw (#6196)
* fix(producer): a render's page-error count includes what the capture workers saw

* fix(producer): a failed parallel capture still carries its workers' page errors

* fix(producer): every capture failure carries the page's errors, in every stage

* chore(engine): drop a comment the capture classifier's name already says
2026-10-24 10:33:03 +00:00
Miguel Ángel 0a8a78edec fix(engine): renames ride out Windows file locks instead of failing the render (#6210)
* fix(engine): renames ride out Windows file locks instead of failing the render

* fix(producer): the true-peak and segment-manifest renames ride out Windows file locks too
2026-10-24 09:51:28 +00:00