Commit Graph
1204 Commits
Author SHA1 Message Date
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
Miguel Ángel c16b783f5e chore: release v0.8.192 (#6206) 2026-10-24 08:51:29 +00:00
Miguel Ángel 381b654335 fix(engine): a clip with data-duration and no data-end ends where its duration says (#6195)
parseAudioElements now resolves end the way core's compileTimingAttrs does. A clip
with no length at all warns, naming the clip, instead of silently playing to the end of
its source.
2026-10-24 08:35:15 +00:00
Diego López e3881bc589 test(engine): synthesize the untagged AV1 fixture with whichever AV1 encoder ffmpeg has (#5379) 2026-10-24 08:15:40 +00:00
Akshar Patel c9ab2796af fix(engine): repeat looped audio through its export slot (#6184) 2026-10-24 07:18:32 +00:00
Miguel Ángel 53e1dfce9e fix(engine): h265 renders no longer fail on hosts with more than 16 cores (#6193)
* fix(engine): h265 renders no longer fail on hosts with more than 16 cores

libx265 rejects more than 16 frame threads, so the thread count taken from the
CPU quota is now capped at 16 for x265. x264 is unchanged.

* fix(engine): pass the encoder to the thread cap in the CFR re-encode
2026-10-24 05:33:56 +00:00
Miguel Ángel 3ef28e26c6 test(engine): the GPU probe tests do not start a Chrome from the host cache (#6187) 2026-10-24 04:33:57 +00:00
Miguel Ángel acb47de7d2 perf(producer): the audio task finds sound with a probe and reads URL clips in parallel (#6174)
* perf(producer): the audio task probes each video for sound instead of extracting its frames

* test(producer): the audio task finds a video's sound from a probe, with no frames extracted

* perf(engine): the audio mix reads clips held at a URL many at a time

* style(engine): format the remote clip slot choice

* fix(producer): the audio task places a video's sound in the window extraction would give it

* style(producer): format the slot cases of the script-src audio test
2026-10-24 04:23:15 +00:00
Abdullah M. Nazif ba5dde78de fix(engine): preserve Windows RSS samples after a query failure (#5357) 2026-10-24 04:10:28 +00:00
Abdullah M. Nazif cad57bf2aa fix(engine): recognize spaced closing template tags (#5353) 2026-10-24 04:10:26 +00:00
Abdullah M. Nazif bd4d8c6cb5 test: use supported FFmpeg flags for VFR fixtures (#5352) 2026-10-24 04:10:24 +00:00
Abdullah M. Nazif 6fccefe976 fix(engine): retain fresh frames when replacing an empty cache (#5351) 2026-10-24 04:10:21 +00:00
Miguel Ángel da3e9b6385 perf(engine): a site's remote files are probed once per render, not once per clip (#6172)
* perf(engine): remote sources of a site keep their probe results for the whole render

* test(engine): probe a content-addressed URL once per render

* style(engine): format the audio probe split
2026-10-24 02:51:47 +00:00
Miguel Ángel cf7266d815 chore: release v0.8.191 (#6170) 2026-10-24 01:40:50 +00:00
Abdullah M. Nazif aac0e7df51 fix(producer): make sparse keyframe advice conditional (#5343)
* fix(producer): make sparse keyframe advice conditional

* fix(producer): clarify sparse keyframes do not affect rendering
2026-10-24 00:43:14 +00:00
Dante 828adc1ce7 fix(engine): await seek completion during drawElement batches (#5430) 2026-10-24 00:28:13 +00:00
kevin c478110be7 fix(engine): redraw a small drawElement frame before failing the serial capture (#5748)
The serial guard in captureFrameSurface, which segmented capture reaches, threw on any frame under the
size floor. It now draws the frame once more and keeps a byte-identical redraw, as the streaming drain
guard does, so a flat frame no longer fails a segmented render.

Fixes #5685
2026-10-24 00:28:06 +00:00
Miguel Ángel 19cc96e3f2 test(engine): only h264 is held to identical bytes across host sizes (#6167)
x265 output varies between runs on one host, so that case flaked the merge queue.
2026-10-24 00:15:10 +00:00
Vance IngallsandClaude Sonnet 5.5 e077d9dc74 fix(core): a hidden child of a mover stays out of the blur trail (#6155)
The isolation CSS re-showed every descendant of a mover, so a child the
author hid with visibility:hidden was cropped and smeared into the trail
(240 green px in the repro; 0 in the sharp frame). The hide rule now skips
the mover subtree and the mover alone is made visible, so its descendants
inherit and keep their own visibility.

Found by review of #6106.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:37:41 -07:00
Miguel Ángel 44100a5370 fix(sdk): a kept delete drops its media sources and a hidden video is not waited for (#6163) 2026-10-23 23:24:04 +00:00
Miguel Ángel 3439433d67 perf(aws-lambda): a render reads a video's audio from a small file stored beside it (#6162)
* perf(aws-lambda): a render reads a video's audio from a small file stored beside it

A video's audio is interleaved with its pictures, so mixing it from the cloud read the whole video (2.5 GB for a 20-minute 4K film) at the Lambda's network speed. lambda sites create now stores each large video's audio packets as a separate object keyed by the video's hash; the audio task reads that when it exists.

* chore(producer): shorten the audio sidecar header comment

* fix(aws-lambda): no audio sidecar for a video whose audio starts late or that has several tracks

* test(producer): format the audio sidecar tests and end their ffprobe options with --
2026-10-23 23:12:52 +00:00
Miguel Ángel 8df9b6dd3b fix(engine): a composition whose script throws is skipped with a warning, not refused (#6156)
* fix(engine): a composition whose script throws is skipped with a warning, not waited on and refused

A script that threw before registering its timeline kept the capture waiting the full 45 s readiness timeout, then the render was refused. After a page error with timelines still missing the wait now ends after a short grace, each such composition gets an empty paused timeline of its authored length, and a script_error_isolated warning names the element. The render goes on without that animation.

* test(engine): memo test expects a script failure, format the browser test

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

* fix(engine): isolate only nested compositions that threw, never load failures or a root

* fix(engine): a thrown composition script fails fast and names the composition, not a load error

* fix(engine): the page-error grace starts at the first error, not at the start of the wait

* fix(engine): a nested throw is skipped only while another nested timeline is live
2026-10-23 22:33:05 +00:00
Miguel Ángel e48d5d4a5c chore: release v0.8.190 (#6158) 2026-10-23 21:54:34 +00:00
Vance IngallsandClaude Sonnet 5.5 79b57ab12d feat: automatic tier, 240°/32 defaults, check finding, and Studio stops opting out (#6108)
* feat: check names the filter fallback, the render log names the tier, Studio stops opting out

hyperframes check reports motion_blur_filter_fallback (info) with the reason capture could not
run and the way back to it. The engine's per-session log line gains tier=, and its pixel test
covers the fallback over a WebGL page. Studio no longer sends previewMotionBlur: every Chrome
can preview blur now, through the fallback where it lacks html-in-canvas; the server still
accepts false from an older Studio.

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

* feat(core): blur picks the cheaper filter tier when every mover only translates

The scan now notes whether any mover turns or scales (the linear part of its transform chain
changes) and whether it already forms a stacking context while it moves. When every mover only
translates, the filter tier draws as well as capture and costs about what no blur costs: on an
8 s bench shot a render took 7.0 s against 9.9 s with capture and 7.3 s with no blur. Anything
that turns or scales, or moves without a transform, stays on capture.

data-motion-blur-tier="capture" or "filter" forces a tier. The report says which one drew
and why (translation-only, tier-attribute); hyperframes check stays quiet for those two, which
are choices, not fallbacks. The pixel tests run the shared assertions through both tiers.

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

* docs(motion-blur): the two tiers, the tier attribute, and what changed in limits

The guide described blur turning off for WebGL, a root transform and similar. It now says the
filter tier draws instead, how the tier is chosen and forced, what each does with a turn or a
zoom, the measured cost of each, and which findings check emits. The skill note drops the
off-for-WebGL advice and the 1.7x figure.

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

* feat(core): blur defaults match a 240 degree shutter and at least 32 samples

The shutter opens 240 degrees by default (was 180) and each mover gets at least 32 taps (was
sized to the smear alone, down to 2), growing with the smear as before. These are the settings
the shutter blur Jake built uses; on the card whip the default render is now 51 dB from his
(34.6 dB at the old defaults). The guide's attribute table says so.

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

* chore(core): trim tier.ts comments to the ratchet

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

* chore: allow deleting the Studio previewMotionBlur render test

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 14:20:17 -07:00
Miguel Ángel 73718a251b fix(engine): the BeginFrame start-up check waits for the first frame up to its deadline (#6149)
* fix(producer): a chunk with mixed capture modes retries whole in screenshot mode

* fix(producer): format the mixed capture mode change

* fix(engine): the BeginFrame start-up check waits for the first frame up to its deadline

* style(engine): format the probe tests

* fix(engine): drop a comment from the probe loop
2026-10-23 20:52:14 +00:00
Miguel Ángel 01a523bcf8 fix(engine): a render no longer decodes the videos it replaces with extracted frames (#6142)
* fix(engine): a render no longer decodes the videos it replaces with extracted frames

* fix(engine): shorten the render-frame video hold comment

* fix(engine): keep the render-frame video hold comment under the line limit

* fix(engine): hold only the videos whose render id is replaced by extracted frames

Two scenes can share a video id. The render id tells them apart, so a live video is no longer held.
2026-10-23 20:52:08 +00:00
Vance IngallsandClaude Sonnet 5.5 4ed7813387 feat(core): capture blur falls back to per-element filters where capture cannot run (#6107)
* feat(core): capture blur falls back to per-element filters where capture cannot run

html-in-canvas cannot capture a WebGL canvas, an iframe or a transform on the root, and some
browsers lack it. Blur used to turn off for the whole composition. Now the runtime hands the
composition to a filter tier: an SVG filter on each mover, 32 offset copies averaged along its
sampled path, softened along the motion so long smears do not step. It filters the live DOM,
so it works over a WebGL canvas, in any Chrome, and leaves glows and shadows on the element.
Turn and scale only soften; capture draws those truthfully, so it stays the first choice and
a scene re-init tries it again.

Capture turning itself off for a defect or three failed captures hands over; the renderer's
explicit off (HDR, distributed, engine blur) stays off. The report names the tier. Core's
unpacked size budget goes to 3,965,000 for the tier.

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

* test(engine): the backface pages now take the filter tier

The two backface tests still expected blur off; with the filter tier a
backface page reports tier filter. Fails on CI without this.

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:46:21 -07:00
Miguel Ángel 1dc97921cd fix(engine): a long audio mix prepares its clips a few at a time (#6132)
* test(engine): the URL-read audio mix test stops its mix before cleanup and is not timed

* test(studio): bank the open-film thumbnail counts on the CI Chrome

The CI Chrome moved to 155 and renders one thumbnail fewer: 13 renders, 14 requests.

* fix(engine): a long audio mix prepares its clips a few at a time

A 200-clip mix started 200 ffmpeg at once and starved the host.

* chore(engine): trim the long comments in the audio mixer

Reasoning moved to the PR.

* test(engine): the mix order test starts every preparation at once

The mixer prepares clips in a core-sized pool; the test now sets enough cores.
2026-10-23 18:24:50 +00:00
Miguel Ángel b7e2278e24 chore: release v0.8.189 (#6140) 2026-10-23 18:13:30 +00:00
Miguel Ángel bd4cae7e35 fix(engine): an opaque ProRes clip extracts to jpg frames, only ProRes with alpha stays png (#6130)
* fix(engine): an opaque ProRes clip extracts to jpg frames, only ProRes with alpha stays png

ProRes was always cached as lossless PNG in case a missed tag hid alpha. ProRes alpha shows in its pixel format (yuva444p), so the guess only costs size: a 4K 4:2:2 clip wrote about 17 MB a frame, 18 GB for 36 s. VP8 and VP9 keep PNG because their alpha rides a side channel ffprobe can miss.

* test(engine): ProRes extraction format follows the alpha plane

* chore(engine): format the ProRes extraction change
2026-10-23 14:14:06 +00:00