* fix(producer): HDR sources are tone-mapped for formats that cannot carry HDR
* refactor(producer): one owner for which output formats can carry HDR
* refactor(producer): drop comments the helper names already carry
* fix(engine): SDR clips are not lifted to HDR when the render tone-maps to SDR
* fix(engine): extraction waits out the decode up to a deep seek in a long-GOP video
* chore(engine): shorten the keyframe gap doc comment
* fix(engine): cancelling an extraction during the keyframe probe reports a cancel
* fix(engine): a segment stopped by another's failure reads as stopped, not cancelled
* fix(preview): the proxy copy for the video at the playhead is made ahead of the rest
* test(preview): the resolveProxy assertions carry the priority option
* fix(preview): the proxy copy asked for last goes first, and a repeat ask moves it to the front
* refactor(preview): the proxy wait loop moves its header and progress handling into helpers
* docs: links point at hyperframes.studio, the app's new home
* docs: the registry guide publishes to hyperframes.studio
* chore: format README table, regenerate the skills manifest
* docs: leave the browser editor's card wording for its own change
* ci: change detection fetches the history its diffs read, not every branch and tag
The Detect changes job checked out with fetch-depth 0, which fetches all 1,400
branches and 670 tags to diff two commits; twice that stalled for the whole
minute and took a pull request out of the merge queue. It now checks out the
commit and fetches its ancestry and main without file contents.
* ci: no change-detection or reachability checkout can run into the one-minute limit
The Detect changes jobs of four workflows and the reachability job kept a
one-minute checkout limit with a retry; two of them hit it together and took a
pull request out of the merge queue. Each now has one checkout with three
minutes, change detection shares one fetch-history step, and the reachability
job, which reads only the base commit, fetches two commits deep.
* feat(cli): render --prores-profile picks ProRes 4444, hq, 422, lt or proxy for MOV
* fix(cli): drop a comment line that raised the comment share
* fix(cli): the render plan args type carries --prores-profile
Publishing a registry file (every block file and hyperframes.lock.json) renamed
its staged copy over the destination with a plain rename, which Windows refuses
with EPERM while antivirus or a just-exited process still holds the file. It now
uses the shared rename that waits and retries on those errors.
* fix(cli): a killed remove-background run no longer leaves its CoreML temp files behind
On Apple Silicon, CoreML writes about 340 MB of compiled models to the system
temp folder for every session and removes them only when the session is
released. A run stopped by a signal never released it, so every cancelled run
left them. A signal now stops the run through the CLI's shutdown, which
releases the session, and the next run removes what a killed run listed in its
own manifest.
* style(cli): wrap the CoreML shutdown comment to the line limit
* fix(producer): stop HDR video windows after the first from repeating one frame
A raw HDR window seeks to a whole second, then trims its lead-in frames.
trim keeps their timestamps, and the raw muxer writes none, so when the
input has an audio track FFmpeg's default CFR sync pads from pts 0 with
copies of the window's first frame and -frames:v cuts the real ones off.
Phone footage always has audio, so HLG clips rendered at ~half their frames.
Pass frames through as the filter graph emits them.
Fixes#6556
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(producer): hold the first frame until a delayed HDR video starts
Window slots counted from the first decoded frame, so a video stream that
starts after its audio played early by its lead. Slots now count from the
extraction start and show the first frame until the stream begins, as they
did before windowed extraction (#6304) and as Chrome does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(producer): add an HDR fixture whose clips carry audio and span extraction windows
Neither HDR fixture had an audio track, which is how #6556 shipped. Window A
checks frames after the first extraction window are not repeats; window B
checks a video stream that starts 0.5 s after its audio holds its first frame
until the picture starts. The baseline was generated in Docker and the fixture
fails against main.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): check fails a full-frame element that covers every other clip, naming it
* test(cli): shorten the cover scene helper's comment
* chore(skills): refresh the manifest for the check reference
* fix(cli): full-frame b-roll over a backdrop is not a covered canvas
* feat(cli): doctor names the tested ffmpeg build and says loudness is only comparable on it
* style(cli): format the doctor ffmpeg note
* fix(cli): drop the doctor ffmpeg comments the comment ratchet counts
* style(cli): format the doctor ffmpeg match
* fix(cli): capture waits for a mid-read navigation to settle and reads the page again once
A site that redirects or reloads after load destroyed the execution context under a page read, and the capture failed with Execution context was destroyed. The capture page's evaluate now settles the new document and runs the read once more on that error only.
* fix(cli): the capture browser opens every page with reads that ride out a navigation
* fix(studio): keep the canvas outline off the scaled preview iframe
Co-authored-by: Ryan Heinl <ryan@siyglobal.com>
* fix(studio): keep NLEPreview under the file size limit
Co-authored-by: Ryan Heinl <ryan@siyglobal.com>
* fix(studio): bank the player's colour-literal baseline
Co-authored-by: Ryan Heinl <ryan@siyglobal.com>
---------
Co-authored-by: Ryan Heinl <ryan@siyglobal.com>
A first page of a fresh Chrome stalled past vitest's 10 s hook limit on a contended Windows runner and failed the first test of its file. The first page is now opened inside beforeAll, with the launch and first-page times logged, and the browser project's hooks get the 30 s the CLI's Chrome project uses.
* test(cli): Chrome-driving tests run in their own project after the rest
A Chrome launching beside hundreds of parallel test files was starved past Puppeteer's 30 s launch limit on a 4-core runner. The motion-signature launch also never passed the 120 s ceiling its comment names.
* test(cli): the process tests that start Chrome run in the chrome project too
* test(cli): name the Chrome process tests as one literal glob the reachability check can read
* fix(cli): a nearer copy of another version no longer hides the host's pinned onnxruntime-node
* fix(cli): load the pinned copy by the entry its exports name, and skip a broken stray manifest
* fix(cli): the pinned copy's entry must sit inside the pinned folder, whatever the folder is named
* fix(producer): a taken file-server port fails the render, and the late grading test runs in its lane
* test(producer): run the taken-port file-server test under Node, where listen errors are events
* test(producer): every *.integration.test.ts must sit in the integration lane
* fix(producer): drop the explanatory comment on the file-server error handler
* fix(studio-server): a history lock whose start cannot be recorded is given back
claim() took owner.pid and then wrote the start record; if that write threw, the error left owner.pid held by this process, so later opens waited on and then named their own pid as the busy owner. Creation and recording now release the file together.
* refactor(studio-server): drop the comment the ratchet counts against ownerLock.ts
* fix(cli): every managed browser closes within a second even if Chrome never exits
Commands called browser.close() directly, which waits for the Chrome process to exit without a limit. A headless shell that acknowledged Browser.close and then stayed alive left the CLI hanging after its work was done. The managed browser's close() is now the bounded close that kills a browser that has not exited.
* test(cli): browser stand-ins carry the close() a real browser has
Audio effects launched Chrome with its own two flags and no retry, so inside
an agent's macOS seatbelt Chrome died at startup with "Target closed" while
the CLI's own browser launch recovered. The sandbox launch policy now lives
in the engine; the engine's Chrome launch retries once in one process over a
pipe when the sandbox refuses it, and audio effects use the shared Chrome
flags in software GPU mode. The CLI imports the same policy from the engine.
* fix: phone videos with an undecodable first audio track check, preview and render
* fix(engine): a rate-lane clip bakes its ramp from the decodable audio track
* test: phone-clip tests end ffprobe options before the input
* fix(studio-server): an HDR clip previews and snapshots the way the SDR export renders it
Footage tagged HLG or PQ played raw in the preview, where the browser tone-maps it its
own way, while the SDR export tone-maps it with zscale and hable: the same clip came
out lighter and with different reds and blues. HDR video now takes the tone-mapped
preview copy (the filter the export uses), and a snapshot still of HDR footage runs
the same tone map. Without an ffmpeg that has zscale and tonemap the clip plays
natively, with a warning that names the missing library.
* fix(studio-server): an HDR clip is never playable for preview, whatever its codec
Only HDR H.264 and ProRes took the tone-mapped copy: an HDR HEVC, VP9 or AV1 clip kept
its codec's test type, so a browser that can decode it was judged able to play it and
the original played with the browser's own mapping. An HDR clip now has no test type, so
the swap always fires. The missing-library warning no longer names the file.
* test(cli): the HDR still test ends ffprobe's options before its input
The repo's ffprobe argv contract requires "--" immediately before the input path; the new
test passed the still's path without it.
* fix(studio-server): preview loads root-path media, images and CSS from the project, as render does
* fix(studio-server): preview writes root paths project-relative, the form Studio reads back
* test(studio): record each edit-accuracy shard's file writes and reloads when HF_WRITE_TRACE is set
* style(studio): format the write trace
* fix(core): hash the write trace with sha256
* style(studio): wrap the trace hash helper
* style(studio): format the trace hash helper
* fix(studio): turn the reload log on in the Studio page the trace watches
* fix(core): text a script splits into letters shows once and carries the variable's value
* fix(core): Studio's mounted and swapped scenes read bound text, and instance values load first
* test(core): the scoped variables table is emitted compact in head
check gave a scene 3 s to signal it was ready, then warned "checking the current page
state" and sampled whatever was on screen. Scenes that build procedural textures or
WebGL canvases routinely need longer, so a pass could miss late content with a warning
nobody could act on. The default wait is now 10 s (it ends the moment the page is
ready), the warning says the scene may still be building and to raise --timeout, and
check_report records render_ready_timed_out so the cases can be counted.
The start probe waited for the shell to exit after SIGTERM, so a shell that stalled under load blocked the whole process. Kill it outright at the timeout.
* fix(cli): a browser's orphaned helpers no longer keep a finished snapshot from exiting
The CDP pipe sockets stayed open after Chrome exited whenever a helper process outlived it, so the CLI never reached natural exit. Destroy them when the browser process exits.
* refactor(cli): name the pipe close so launch.ts carries no new comment
* fix(cli): a browser stand-in without stdio no longer throws from the exit listener
* fix(cli): close every pipe of an exited browser, not only the CDP pair
* fix(cli): closing the managed Chrome also ends the helper processes it leaves behind
The close waited a second for Chrome to exit and then killed only Chrome's own process.
Under load that second is often not enough, and the helpers (the GPU and renderer
processes, crashpad_handler) were reparented to init and kept running after a finished
run. The close now records the processes under Chrome before it starts, and ends any of
them still running (under the same command) once Chrome is gone.
* test(cli): the helper-reaping test polls for the helper's exit instead of waiting 200 ms
* fix(cli): the Chrome process listing is bounded and no longer runs at import
The ps call that records Chrome's helpers ran with no time limit before the
bounded browser close, so a stalled ps made the close unbounded. It now has a
2 s limit, and a timeout records no helpers.
execFile is resolved when ps runs, not when the module loads, so tests that
mock node:child_process without execFile import it again.
* fix(cli): audio --read starts music where its sound is sustained
A run of the bed is split where sound fills less than half of the surrounding
2 s, so a short tone and scattered clicks that bridge into the music no longer
pull its start earlier. Sparse stretches are left to effects.
* fix(cli): audio --read finds effects in each stem and levels them from the mix
A sound ends where the next one starts, so a run of kicks is not one long
riser. Effects are searched in the drums and in the bed outside music, each on
its own, so a click inside a tone is still found. Each effect's peak is read
from the decoded mix in dBFS, and the music reference is taken next to the
effect rather than under it, where its own leakage sits.
* fix(cli): audio --read measures the recording through each pause
Pauses carry the whole recording's level so the read can say what keeps
playing while the voice stops.
* feat(cli): audio --read prints peaks, what plays in pauses, voice vs music
Effect lines print the absolute peak and name the reference of the relative
figure; pause lines keep the pause kind and list the effects, music and
ambience under them; sentences over music print voice and music loudness and
which is louder. Read schema version 4.
* fix(cli): audio --read finds bed effects on the whole bed, not a blanked one
Blanking the bed wherever music or ambience played made every blanked
stretch that ended while sound was still audible look like a sound rising
out of silence. Fades and quiet bridges then printed as dings, hits and
whooshes. The bed is now searched whole, and each source carries the
spans it plays music or ambience in. A sound whose onset-to-peak overlaps
one of those spans belongs to it, so it is not an effect. The drums and
the bare mix carry no spans.
— Jerrai
---------
Co-authored-by: Jerrai <jerrai@heygen.com>
* fix(cli): beats given a media file points to hyperframes audio instead of a folder error
* fix(cli): beats names the folder for a composition file and syncs the skills manifest
* fix(cli): snapshot's auto end frame stays within 250 ms of a long composition's end
* test(cli): the end-frame test comment states the cap, not the history
* chore(cli): shorten the snapshot end-frame comment
The GPU probe's answer is kept for the whole process and the first caller's probe is the
one every later caller waits on. When that first caller cancelled it (a prelaunched
browser dropped, a stopping check), the probe died with "This operation was aborted",
and that cancellation was kept as the answer: every browser the process launched after
it ran in software mode, with a log line blaming the GPU probe. A cancelled probe is now
forgotten and the next caller probes again.
* fix(studio-server): preview resolves root-path module imports to the project
* test(studio-server): preview's own scripts make no module import; lint flags a broken import map
* fix(cli): audio --read reports noise beds as ambience, not music
A stretch of the non-voice stems counted as music whenever it was above
-55 dB, so room tone and engine rumble separated into "other" printed as
a music piece. A stretch is now music only with a beat, clear tonal chroma
or more than one leading instrument; otherwise it is ambience with its
level in dBFS, called quiet under -45 dBFS, and breaks lie only between
two runs of music. The header lists only stems that carry sound, with
their level, instead of the separator's six fixed names.
* refactor(cli): audio --read gate carries its meaning in names, not comments
* fix(cli): keep ambience out of downstream music measurements
* fix(cli): measure music beat evidence within each stretch