Commit Graph
6474 Commits
Author SHA1 Message Date
Miguel Ángel c9377e28ff test(cli): the studio server test reads the asset body it fetches before moving the folder (#6528)
The file-change test fetches an asset and never reads the response, so the file stays
open and Windows fails the later folder move with EPERM (it failed a merge-queue run).
The helper now reads each body to its end.
2026-10-27 07:28:55 +00:00
Miguel Ángel 592ca3daf1 fix(studio-server): history lock retries a busy url write and releases on failure (#6525)
* fix(studio-server): history lock retries a busy url write and releases on failure

* style(studio-server): wrap the fs mock

* style(studio-server): format the owner lock test
2026-10-27 07:09:09 +00:00
Miguel Ángel a669316bf2 fix(cli): --timeout on check and snapshot takes units like 45s (#6521)
* fix(cli): check stops making video copies after 10 minutes, and --timeout takes units

The copy phase of check had no overall limit: a source that kept moving was never
stopped, and stdout stays silent the whole time. It now ends after 10 minutes with
an error naming the files still unfinished and the elapsed seconds; finished copies
are reused on the next run. The stderr progress lines are unchanged.

--timeout on check and snapshot is milliseconds, so "--timeout 45" waited 45 ms.
It now accepts 45s, 2m and 45000ms, and a bare number under 1000 warns on stderr
that it is milliseconds. Output on stdout is unchanged.

* chore(cli): cut the --timeout parser comment to the comment limit

* fix(cli): drop the 10 minute wall clock on check's video copies

A copy that keeps making progress must not be cut by a clock, and a re-run would
only start it over. The copy already ends when ffmpeg stops reporting progress
for 3 minutes (finished pieces are kept for the next run), so the phase is
bounded on no progress. The --timeout warning now names the value actually used
when check raises a small value to its 500 ms floor.
2026-10-27 06:55:05 +00:00
Miguel Ángel 808d642bce fix(cli): take JSON flags from a file or stdin so PowerShell can pass them (#6518)
* fix(cli): take JSON flags from a file or stdin so PowerShell can pass them

* fix(cli): read BOM-prefixed and UTF-16 JSON from a file or stdin

* style(cli): format the JSON flag reader

* test(cli): feed the bytes PowerShell pipes to the JSON reader

* style(cli): shorten the JSON flag reader's comments

* style(cli): one line for the JSON flag reader's comment
2026-10-27 06:33:50 +00:00
Miguel Ángel 859cd2fb9c fix(core): retiming a parked scene in the preview takes effect without a reload (#6522)
* fix(core): retiming a parked scene in the preview takes effect without a reload

* fix(core): an edit that leaves a parked scene without a start or length in seconds reloads instead
2026-10-27 06:15:38 +00:00
Miguel Ángel 33e0ba550b fix(core): an audio clip that never gets data no longer freezes the playhead (#6523) 2026-10-27 06:10:26 +00:00
Miguel Ángel 594df21537 fix(core): a video after a cut plays in sync instead of about 70 ms behind (#6494)
* fix(core): a video after a cut starts early by its measured start delay, so it plays in sync

* test(player): playbench reports how far the picture after a cut runs behind the playhead

* fix(core): a looping video no longer skews the learned video start delay

* fix(core): the video start delay is learned in less code and capped at 100 ms under test
2026-10-27 06:09:05 +00:00
0fb9a50a63 fix(docker): Node 22.23.4 and Bun 1.4.4 for CVE-2026-111497 (Brotli) (#6440)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Joel Gallardo <joel.gallardo@heygen.com>

Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com>
2026-10-27 05:41:56 +00:00
Miguel Ángel 527ab66905 test(engine): Chrome-driving tests run in their own project after the rest (#6517)
* test(engine): Chrome-driving tests run in their own project after the rest

* ci: test reachability understands inline vitest projects
2026-10-27 05:38:33 +00:00
Akshar Patel 2d207cb3a5 fix(cli): validate cloud variables against the selected composition (#6520) 2026-10-27 05:20:00 +00:00
Miguel Ángel bd9b134cbb fix(core): the npm package no longer ships the unminified media sync runtime (#6515)
* fix(core): the npm package no longer ships the unminified media sync runtime

* style(core): a two-line comment for the media clip's volume keyframes

* style(core): the moved media clip type keeps only the comments its names do not say
2026-10-27 05:03:45 +00:00
Xuanru LiandJerrai a779206430 feat(cli): capture accepts a local HTML file again (#6498)
* feat(cli): capture accepts a local HTML file again

Adds an explicit --local-file mode. The file's directory (or --local-root) is served
on a loopback port that only Chrome may reach outside the capture proxy; all other
traffic keeps the public-address policy. The URL argument stays public HTTP(S) only.

* fix(cli): local capture output names the file, not the loopback server

In --local-file mode, meta.json, AGENTS.md, extracted files and the returned result
used the temporary 127.0.0.1 origin. One rewriter maps that origin to file:// URLs
under the root, and meta.json records the file as its source. Root and served paths
now resolve with the same native realpath, which Windows needs.

* fix(cli): local capture reads its files from disk and resolves real paths natively

The asset downloader read the local page server with Node's fetch, which an environment proxy
(NODE_USE_ENV_PROXY with HTTP_PROXY) intercepts, so local images and fonts were dropped. It now
reads the file from disk under the server's own checks.

Root and served paths both use the native realpath. The JS realpath kept Windows 8.3 short names
while fs/promises expanded them, so every file answered 404 on Windows.

The default root is the directory the file was named in, so a symlinked entry can no longer widen
it to wherever the link points.

The local-file docs move below the capture policy text so their heading does not swallow it.

* refactor(cli): capture settles a watchdog outcome in one place

* fix(cli): escape every RegExp metacharacter in the local origin rewriter

---------

Co-authored-by: Jerrai <jerrai@heygen.com>
2026-10-27 04:45:32 +00:00
Miguel Ángel 619b8bc4d7 chore: release v0.8.207 (#6510) v0.8.207 2026-10-27 04:33:06 +00:00
Miguel Ángel eb6b2c9de9 fix(cli): snapshot counts and lists the stills that are on disk, and fails when some are not (#6511)
* fix(cli): snapshot counts and lists the stills that are on disk, and fails when some are not

The summary counted frames the run had captured, not files that exist, and printed paths
relative to the project. It now checks every still on disk, prints absolute paths, says
"N of M" when some are missing or empty, names them, and exits non-zero.

* fix(cli): snapshot says when it clears an earlier run's stills from the folder

Without --keep, a run removes the stills an earlier run left in the folder before it captures,
so two runs into one -o folder silently erased the first. The behaviour stays; the console
and --json now say "cleared N stills from an earlier run in <folder>". The on-disk comment is
also cut to the comment limit.

* fix(cli): snapshot judges the stills it wrote, not the frames it was asked for

A --zoom target that is not visible skips a frame on purpose, with a warning, and that frame never
reaches the written list. Comparing the files on disk with the requested count turned that into a
failed run. Only a written still that is missing or empty fails now.
2026-10-27 04:29:40 +00:00
Miguel Ángel ac90833cc6 fix(player): fade the controls when idle and copy crossorigin to the audio it plays (#6509)
* fix(player): fade the controls when idle and copy crossorigin to the audio it plays

* fix(player): only keyboard focus keeps the controls on screen
2026-10-27 04:09:05 +00:00
Miguel Ángel 202bed36c9 test(producer): the stall tests jump fake time to the next timer instead of a second at a time (#6508)
* test(producer): the stall tests jump fake time to the next timer instead of a second at a time

A 400 s scenario took 400 steps of 50 real event-loop turns plus file IO, which crept
past the unit timeout on a loaded runner. Idle windows now cost one step.

* test(producer): stub the memory sampler so the stall tests' timer jumps skip the idle windows

The sampler is a faked 250 ms interval, so it was the next timer on every jump and the
jump was never longer than 250 ms.
2026-10-27 03:51:58 +00:00
Miguel Ángel be036160fe fix(cli): Kokoro speech works when its espeak data path passes 260 characters (#6507)
* fix(cli): Kokoro speech works when its espeak data folder path passes 260 characters on Windows

* test(cli): cache test follows the synth script version
2026-10-27 03:22:49 +00:00
Miguel Ángel 142f246d7c fix(cli): a finished capture no longer fails when Windows refuses to delete its Chrome profile (#6505)
Chrome's profile lockfile is held for a moment after the browser exits, so removing the
capture profile threw EBUSY and a finished capture exited 1. The profile and staging
removals now go through a core helper that retries transient locks and, for cleanup of
finished work, warns instead of failing.
2026-10-27 03:10:11 +00:00
Miguel Ángel c618724a5f fix(producer): a clip bound by its mount's variable values plays the bound file (#6501)
* test(producer): probe a variable-bound clip under a host with values

* fix(producer): probe a variable-bound clip its mount or defaults rebind

* style(producer): variable media fixtures in small steps

* test(producer): list the real-render variable mount test in the integration lane

* style(producer): keep probeStage's comment share at the base
2026-10-27 03:09:31 +00:00
Xuanru LiandJerrai 53d3caeac5 feat(cli): capture can route its traffic through an opt-in upstream proxy (#6499)
* feat(cli): capture can route its traffic through an opt-in upstream proxy

HYPERFRAMES_CAPTURE_PROXY=http://host:port sends the capture browser proxy's
forwarded requests and CONNECT tunnels, and every publicRequest download,
to that proxy by hostname instead of dialing a checked public IPv4. Unset,
behaviour is unchanged.

* fix(cli): keep capture comment share flat and close the new CodeQL findings

Test fixtures record what they receive instead of echoing request data, and
capture --video strips line breaks from a failed download's message before
logging it.

* fix(cli): proxied HTTP downloads send no URL fragment, and a malformed proxy credential is refused

The absolute-form request to the upstream used the URL's href, so a fragment such as
sprite.svg#icon went on the request line, which the pinned path never sends and strict proxies
refuse. A malformed percent-escape in the proxy credentials threw a bare URIError instead of the
usual "not a valid proxy URL" error. The test servers label their echoes text/plain.

* fix(cli): strip line breaks from capture --video errors in the form CodeQL recognizes

* fix(cli): capture --video logs errors through a recognized newline sanitizer

---------

Co-authored-by: Jerrai <jerrai@heygen.com>
2026-10-27 02:52:52 +00:00
Miguel Ángel 3ec054ab6f fix(engine): renders on a Mac decode a clip in software when the media engine refuses it (#6488)
* fix(engine): renders on a Mac decode a clip in software when the media engine refuses it

* refactor(engine): name the software decode retry condition instead of commenting it
2026-10-27 02:52:25 +00:00
Xuanru Li 725f069cb9 feat(cli): skip the preview-server scan when HYPERFRAMES_NO_PREVIEW_DISCOVERY=1 (#6503)
render, check and snapshot open project history, which looks for a running
preview by probing 100 loopback ports and 5190. Behind an egress fence every
probe is refused and reported. Setting HYPERFRAMES_NO_PREVIEW_DISCOVERY=1 makes
historyDeps.findServer return null without a request, so history opens directly.

Refs #6204
2026-10-27 02:34:19 +00:00
Miguel Ángel 736f38ff4a fix(player): each take plays its own audio when the browser blocks autoplay (#6417)
* fix(player): each take plays its own audio when the browser blocks autoplay

* fix(player): a parent audio proxy of a video clip is seeked when its lag holds, not rate-chased

* style(core): keep the steering policy's comment share where it was
2026-10-27 01:49:09 +00:00
Miguel Ángel 63d9435625 test(cli): install-lock concurrency test uses fake timers (#6504) 2026-10-27 01:48:48 +00:00
Miguel Ángel 6b5d068e4a fix(core): a video no longer runs ahead of the playhead or past its out-point (#6438)
* fix(core): a video no longer runs ahead of the playhead or past its out-point

* fix(core): keep a looping video playing when the playhead wraps before it
2026-10-27 01:45:05 +00:00
Miguel Ángel 7aac5c763b perf(aws-lambda): each chunk logs where its seconds went (#6391)
* perf(aws-lambda): each chunk logs where its seconds went

The chunk_capture event now carries the handler's setup time and the renderer's own stage timings (plan check, browser boot, capture, encode, total), which the renderer already measured but the logs dropped. Lets a fleet run be split into start-up, capture and tail per chunk.

* perf(aws-lambda): chunk logs also time the Chrome path resolution

On the container image the compressed Chrome may be unpacked on every cold chunk; chromeMs separates that from the plan download in setupMs.
2026-10-27 01:24:48 +00:00
Miguel Ángel 4539f35170 feat(cli): write one diagnostic report when a command stays silent for 60 s (#6480)
* feat(cli): write one diagnostic report when a command stays silent for 60 s

* feat(cli): load the hang report only when it is asked for

* fix(cli): the hang report is written without announcing itself on stderr

* fix(cli): the hang report leaves out environment values or is not written, and is written once

* refactor(cli): shorten the hang report comment to the line limit
2026-10-27 01:23:43 +00:00
Miguel Ángel a0b0bf5f64 fix(cli): history show --diff works without git on PATH (#6475)
* fix(cli): history show --diff works without git on PATH

* fix(cli): unifiedDiff passes lint and fallow: blocks, shared direction rule, split test

* refactor(cli): split the diff walk and its backtrack into small steps
2026-10-27 01:23:02 +00:00
Miguel Ángel 5a28b4e7eb fix(lint): a media src whose file name holds # or ? says to write it percent-encoded (#6495)
* fix(lint): a media src whose file name holds # or ? says to write it percent-encoded

* refactor(lint): the url-safe hint encodes with the shared encodeUrlPath
2026-10-27 01:10:49 +00:00
Miguel Ángel 312a8fd9c7 fix(engine): a stalled BeginFrame capture resumes from the first unwritten frame (#6490)
* fix(engine): a stalled BeginFrame capture resumes from the first unwritten frame

A wedged worker used to throw away every frame already written and restart the
whole render on a screenshot session. Fresh BeginFrame workers now continue from
the first unwritten frame (once; a second stall still takes the screenshot
fallback), and the stall message names each worker's in-flight frame and step.

Force-closing the wedged Chrome no longer crashes the CLI with an unhandled
pipe ECONNRESET.

* chore(engine): cut the stall-resume comments to the comment limit

The explanations live in the PR body.
2026-10-27 01:10:09 +00:00
Miguel Ángel c21a70915f fix(cli): a preview inside a host app's run no longer takes the project's history (#6493) 2026-10-27 00:33:12 +00:00
Miguel Ángel 8505a4168d fix(studio): a timeline edit that lands on the clip's own values saves nothing instead of failing (#6487)
* fix(studio): a timeline edit that lands on the clip's own values saves nothing instead of failing

* chore: re-run checks against the updated PR description
2026-10-27 00:31:05 +00:00
Miguel Ángel 54d0af57eb feat(engine): render sets a keyframe interval and a peak bitrate cap (#6444)
* feat(engine): encoders cap peak bitrate and place keyframes on a set interval

* style(engine): shorten the rate-cap comment

* style(engine): drop the doc lines the comment ratchet flagged

* feat(producer): mp4 renders honour a keyframe interval and a peak bitrate cap on every encoder path

* style(producer): shorten the delivery encoder file headers

* style(producer): drop a comment line from the segment manifest

* style(producer): shorten the integration test header

* test: delivery encoder tests follow the ffprobe argv contract and run in the integration lane

* feat: the render request, server and Lambda plan carry the keyframe interval and peak bitrate cap

* fix(studio): the Studio render route passes the keyframe interval and bitrate cap to the encoder

* chore(studio-server): drop a comment the helper's name already says

* feat(cli): render and lambda render take --keyframe-interval, --max-bitrate and --buffer-size
2026-10-27 00:29:06 +00:00
Miguel Ángel 95e3930faf fix(core): audio clips join on time and their volume lane follows the audio (#6419)
* fix(core): audio clips join on time and their volume lane follows the audio

An incoming <audio> clip is now pre-seeked to its in-point like a video, and
a playing audio element that drifts is steered by rate instead of seeked
while it sounds. A routed clip's volume lane and fades are re-aimed at the
element's own position whenever it starts, is seeked or changes rate.

* fix(core): sounding audio is nudged at most 10%, and a larger lag seeks under a short dip

Speech audibly changes tempo past a 10% rate nudge, and a bare seek clicks.
Audio that holds a lag rate cannot close in about a second is seeked under a
15 ms dip on its routed graph; an unrouted element is seeked plainly. Video
keeps its steps.

* fix(core): a routed clip's automation has one owner, its element, looping included

A rate change re-aimed a routed clip's lane on the context clock and again
when the element took the new rate. Only the element re-aims it now. A
looping element's wrapped position is placed on the pass the transport is in,
so a looping bed's lane follows it too.

* style(core): wrap a steering comment to the line limit

* refactor(core): one drift policy for playing media, exported for the parent proxy

The steering steps, the audio cap and limit, and the steering state move to
runtime/mediaSteer.ts as steerMedia(el, offset, baseRate, settled) and
forgetSteering(el), exported as @hyperframes/core/runtime/media-steer so the
player's parent-frame proxy uses the same policy instead of a second one.

* style(core): trim the drift policy's comments to the package's share

* fix(core): the dipped seek lands on the frame-start media time

Routes the one currentTime write this branch adds through mediaElementTime,
so Chrome's truncated currentTime cannot land it a frame early.

* test(core): expect the plain audio seek at the frame-start media time
2026-10-27 00:09:27 +00:00
Miguel Ángel 097cc094b7 fix(studio): a reload refused because the page is missing mid-edit warns instead of erroring (#6491) 2026-10-26 23:53:48 +00:00
Miguel Ángel dcec31b468 fix(engine): audio scratch files Windows refuses to delete no longer fail a render (#6474)
* fix(engine): a scratch folder Windows refuses to delete no longer fails a finished audio mix

* fix(engine): audio scratch files Windows refuses to delete no longer fail a render

* fix(engine): a rename Windows keeps refusing falls back to a copy so the audio mix finishes

* fix(engine): audio scratch left behind is counted in the render log

* style(engine): wrap the long rename fallback test call

* refactor(engine): split the rename fallback decision out of the retry loop

* fix(engine): the rename copy fallback is opt-in and scratch leftovers are counted per render

* style(engine): keep renameWithRetry's comment share at the base

* style(engine): drop the blank line left by the comment cut
2026-10-26 23:49:02 +00:00
Miguel Ángel 165744edc0 fix(producer): compile probes each unique media source once however many elements use it (#6441)
* fix(producer): compile probes each unique media source once however many elements use it

* test(producer): run the probe-count test in the integration lane
2026-10-26 23:28:57 +00:00
Miguel Ángel be4e2e7806 feat(cli): hyperframes open hands the project to the app on Windows and Linux too (#6484) 2026-10-26 23:01:07 +00:00
Miguel Ángel 75bee42919 fix(core): paused preview seeks land on the composition's own frame rate (#6460)
* fix(core): preview seeks land on the composition's own frame rate

* fix(studio): frame step, frame readout and ruler use the composition's frame rate

* fix(studio): an end trim previews the clip's last frame at the composition's frame rate

* refactor(studio): ruler labels follow the ticks' frame rate and fps parsing splits by form

* style(studio): format the ruler label helper and its test

* chore(inspector): raise the package size budget to the measured size plus headroom
2026-10-26 22:54:21 +00:00
Miguel Ángel 6b7d6dfb2e test(cli): the mid-render orphan test waits for three Chrome processes alive at once (#6489) 2026-10-26 22:53:12 +00:00
Ular Kimsanov bab9ed58ad chore(cli): tag the studio download link with where it was shown (#6471)
* chore(cli): tag the studio download link with where it was shown

The terminal hint, hyperframes open and the browser editor button now add
src=cli or src=browser-editor to the download link. The download route
already records src, so these downloads stop landing in no source.

* chore(cli): drop the DownloadSource doc comment the type name already says

* chore(cli): leave the no-build fallback link untagged, since only the download route reads src
2026-10-26 21:39:35 +00:00
Ular Kimsanov 930c7dff50 docs(guides): put Open Design and 4K rendering back in their page titles (#6483)
/guides/open-design was folded into design-tools in August and the 4K
guide was retitled "Render in 4K"; Google stopped showing us for "open
design" and "4k render". The titles name them again.
2026-10-26 21:37:51 +00:00
Ular Kimsanov 86dba83972 docs(studio-app): name the Windows app's words, keys and settings folder (#6478)
* docs(studio-app): name the Windows app's words, keys and settings folder

The app says PC where a Mac says Mac and takes Ctrl where a Mac takes Cmd;
its settings live in %APPDATA%\HyperFrames. Cut out object here needs the
Mac image engine (Apple silicon, macOS 14+); Remove background runs on any
computer.

* docs(guides): split picture and video cut out requirements

Pictures cut out on Apple silicon with macOS 14+ or Intel with macOS
13.3+ (imageModels.mjs DARWIN_FLOOR); video cut out needs Apple silicon
(imageIpc.mjs models.video checks arm64).
2026-10-26 21:37:48 +00:00
Miguel Ángel 59d5e4fec8 fix(cli): check and snapshot fail fast on a missing browser path and say while Chrome downloads (#6479)
* fix(cli): check and snapshot fail fast on a missing browser path and say while Chrome downloads

* fix(cli): a non-interactive Chrome download gives up after one stall, naming the env var and doctor
2026-10-26 21:28:41 +00:00
Ular Kimsanov c4d73299bd docs: search keywords so pricing and npx skills searches find their answers (#6472)
Mintlify search keywords on the FAQ (pricing, price, cost, free...) and
the skills guide (npx skills, skills add...). Last week pricing searches
found a clicked result 8% of the time and npx skills 0%.
2026-10-26 21:21:59 +00:00
Miguel Ángel dfd2028f16 fix(studio): a clip that cannot be read gives up after two retries with one typed line (#6477)
* fix(studio): a clip that cannot be read gives up after two retries with one typed line

* fix(studio): a clip whose stream breaks mid-read also fails with the typed line
2026-10-26 21:08:46 +00:00
Vance IngallsandClaude Opus 5.5 08d3745391 fix(producer): retry segmented capture on a screenshot session after a drawElement failure (#6437)
The segmented route had no drawElement recovery: a suspect small frame, a
missing paint record, a renderer stall or a failed self-verification failed
the whole render, where the streaming and disk paths re-render via
screenshot. Catch those errors around the segmented stage, replan with
forceScreenshot (segmentation kept), close the probe and retry once.

The retry discards the segment directory and manifest, including segments
resumed from an earlier run: the plan hash does not cover the capture mode,
drawElement and screenshot segments are not byte-identical, and segmented
drawElement segments are never self-verified.

A screenshot retry whose first segment encoder cannot spawn now keeps
forceScreenshot when it drops to plain streaming.

— Via

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 21:07:21 +00:00
Vance IngallsandClaude Opus 5.5 e47fdad7d0 fix(producer): open a fresh segmented session when the probe has risk frames (#6434)
* fix(producer): open a fresh segmented session when the probe has risk frames

Since #6318 the gate-check probe is planned with deFrameFallback: true, so
it can carry deRiskFrames. The segmented stage reuses that probe for its
first session and captures every frame with captureFrameToBuffer, which
cannot hand a risk frame to a screenshot session: the first one throws
"frame needs screenshot capture, which this capture path cannot hand over"
and the segmented route has no drawElement catch, so the render fails.

Close such a probe and open a fresh segmented session instead; those are
created with deFrameFallback: false and keep the whole-render screenshot
fallback.

— Via

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

* refactor(producer): name the risk-frame probe condition instead of commenting it

The comment ratchet does not allow a file's comment share to rise.

— Via

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-26 21:07:19 +00:00
Miguel Ángel 1df003dfb7 fix(cli): a file given as the project folder names the folder to pass (#6476) 2026-10-26 20:51:08 +00:00
Miguel Ángel 933ae3eb5b fix(engine): a failed drawElement worker encode takes the fresh-session recovery (#6470)
* fix(engine): a failed drawElement worker encode takes the fresh-session recovery

The encode result is awaited after its frame was produced.

Its failure reached the stage as a plain error, so the render ended.

It now surfaces as a drawElement capture failure, like a failed produce.

* fix(engine): drop the comments from the encode failure wrapper
2026-10-26 19:50:02 +00:00