* fix(core): a video plays to the last frame before its out-point instead of stopping a frame early
* fix(core): a dropped frame before an out-point no longer lets the out-point's frame show
* perf(core): a video edit with no animation timeline stops rewriting every clip each frame
* refactor(core): drop the comments the comment ratchet counts
* fix(core): a clip that has fully downloaded stays loaded and plays on time
* refactor(core): drop the comments the comment ratchet counts
* fix(core): the playhead keeps moving at a cut while the incoming take has not started
* fix(core): shorten a comment in the clock
* fix(player): keep the WebKit stops script out of the type check
* fix(core): cut two comments from the clock
* fix(core): a still sub-composition's data-no-timeline on its template root skips the timeline wait
* chore(core): drop a comment the copy already says
* fix(core): the preview's scene window settles when a scene can't unmount instead of hanging the page
* refactor(core): name the scene window's stuck step instead of commenting it
* fix(core): telemetry redaction covers folders with spaces in any script
* style(core): shorten the redaction folder note
* fix(core): spaced folders without a root redact only before a file name
* style(core): drop the redaction note that described the leak as intended
* style(core): two fewer comment lines in the redactor
* fix(core): bound the spaced-folder match so long lines stay linear
* fix(player): iPhone Safari keeps every take's sound after the first seconds of an edit
* fix(player): shorten the priming comment
* fix(player): prime iPhone takes only, and only unmuted ones
* fix(player): shorten a comment
* fix(player): skip only clips authored muted, and drop a doc comment
* fix(cli): a snapshot still shows what playback shows, whatever other times are asked for
* test(cli): pin that still-capture pages ask for the skipped-frame replay
* fix(core): a multi-worker or chunked render lands every frame where an in-order render does
* fix(core): replay only where tweens overlap, and from any off-grid scene time
* fix(core): a film with paused scenes replays only where its tweens overlap
* fix(cli): audio windows read beats off the whole clip; beats snap to the signal and mark silence
A film query that covers part of a clip measures beats, downbeats, bpm and sections on the
whole clip and cuts them to the window, as #6653 did for kind, so a window under 6 s still
has its beats.
One step after every provider ladder moves a provider's beats and downbeats onto the
signal's own beats within a quarter beat (the snapping api did alone) and marks each with
`silent`, true where the beat falls in silence. Beats are marked, never dropped; the JSON
change is the additive `silent` array.
— Jerrai
* fix(cli): beats with no pulse are unheard; a window with none answers no steady beat
Review fixes for #6692:
- The per-beat flag is now `unheard` (renamed from `silent`, not released): true in silence
or where no onset falls within 50 ms of the beat, so hiss, room tone or a held pad no
longer pass as beats.
- A window keeps the whole clip's beats only when at least two of its beats are heard;
otherwise it answers "no steady beat" as before, with no downbeats and no bpm.
- A window's bpm is the tempo of its heard beats, never the whole clip's.
- Comment lines trimmed so analyze.ts, run.ts and api.ts are at or under main's share.
— Jerrai
* fix(cli): a beat is unheard only where two beats in a row have no onset
Scored on the ground-truth set, flagging every onset-less beat also flagged heard beats the
onset detector misses: the drop downbeat after a snare roll, single beats under narration,
quiet beats late in a fade. A pulse gap now needs two onset-less beats in a row; hiss, room
tone and held pads span many beats and stay flagged, and silence is flagged as before.
— Jerrai
* fix(cli): a window with no pulse on the clip's grid reads its own beats, as before
Genesis found a window over a section far under the clip's loudest part answered "no steady
beat" where main, reading the window alone, found every beat: the clip's grid and onsets
skip a section that quiet (from about 40 dB under, for clicks). When a window keeps fewer than
two heard beats from the clip's grid, its beats, downbeats and bpm now come from the window
alone, as they did on main. Hiss still answers "no steady beat" on its own.
The hiss test's noise() repeats every 10,466 samples (0.22 s at 48 kHz), which the window's
own reading took for a pulse; that test now uses a xorshift generator.
— Jerrai
* fix(audio): unheard comes from level-free rhythm evidence, so quiet music stays heard
A beat was flagged by an absolute -50 dBFS silence and by onsets under 10% of the clip's
loudest hit, so music 30 dB or more under the loudest part read as unheard. Now:
- core localBeatEvidence reads the cached standardised onset curve over the 6 s around each
beat: the tracker's steady-beat confidence there (clarity at the local beat lag times the
beat's own lift) and the beat's lift over the span's mean.
- A beat is unheard in digital silence (-80 dBFS), or when its span keeps no steady beat
(confidence under 0.15) and its own onset does not stand out (lift under 1.5). Lift alone
keeps beats under narration heard, where syllables lower the span's clarity.
- A window keeps the clip's beats when at least two, and at least half, of them are heard.
— Jerrai
A composition with no GSAP timeline was treated as zero length after an edit, which dropped preview audio, and an edit made during playback left the transport stopped. The runtime now keeps the declared length when it rebinds, and the soft reload resumes a transport that was playing.
An inert <hf-color-group> holds a lane's data-color-grading; <video>, <img>
and <canvas> join with data-color-group. A member's own grading overrides
its group's. The runtime discovers members, regrades them when the group's
grading or a member's data-color-group changes, and ungrades them when the
group element or its grading goes, drawing only members on screen.
Render keeps group-graded video at full size like directly graded video,
and lint accepts (and still validates) grading on hf-color-group.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
bakeHfColorGradingCube evaluates the runtime shader's colour path on the
CPU (primary, tonal wheels, RGB and hue curves, secondaries, output LUT,
intensity mix) through the same 8-bit texture quantisation, and writes a
red-fastest .cube for ffmpeg's lut3d. Grades with spatial stages (grain,
vignette, effects) throw, as does a grade whose LUT was not passed in.
A Chromium test draws a 4,096-colour lattice through the real runtime
and compares every tile with the bake.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): a font declared in a linked stylesheet no longer trips the bundler's font warning
* fix(core): drop the guard's explanatory comment
* fix(core): bundled fonts named only in a CSS font shorthand load
* fix(core): a font shorthand loads faces but never fails the cloud system-font check
* test(producer): regenerate static-volume-future-set for the font shorthand face
* test(producer): regenerate two more fixtures for the font shorthand face
* fix(core): a preview media load that fails is reported, not swallowed
* fix(core): an audio fed by source children reports its failed load; the shape lives in the protocol
* fix(core): the audio load watcher finds elements from any realm
* build(core): regenerate the inline position-edits render script
* fix(audio): a sound is voice only with a voice-range pitch, judged on the whole clip
Two ways the kind guess called a non-voice sound voice:
- A short hit, click or whoosh can have a speech-shaped envelope. classify() now also
measures the share of 40 ms frames with a 70-500 Hz fundamental (YIN, moved from the
CLI into core) and calls a sound voice only when that share reaches 0.15. Speech
measured 0.22-0.43; impacts, clicks and whooshes 0.
- A range query judged a clip's kind on the slice it covered, so 1.7 s of a music bed
read as voice. A film clip's kind is now judged on all of the clip that plays.
Even-paced speech (a 64 s TTS take) moves less at syllable rate and was called music;
with no beat and a voiced share of 0.25 or more it is now voice.
* chore(audio): keep each touched file's comment share at or under its base
The Comments check failed on run.ts, targets.ts and audioAnalysis.ts because the
PR added comment lines to files whose share may only go down. Drops the added
doc comments; no code change.
— Jerrai
* fix(audio): preserve clip evidence and stereo pitch during analysis
* fix(audio): bound and cache whole-clip kind analysis
* fix(audio): retain native-rate kind evidence when available
---------
Co-authored-by: Jerrai <jerrai@heygen.com>
* fix(inspector): live queries see every composition, wherever the playhead is
The Studio preview parks scenes away from the playhead, so a live inspect, timeline or
element-map query answered missing for every element in a parked composition. Each live
query now holds every scene mounted under its own holder, then hands the scene window back.
__hfHoldScenes keeps holds per holder, so the inspector letting go never drops a scene
Studio holds.
* fix(inspector): isolate concurrent live query scene holds
* fix(studio-server): undo and redo on Windows restore a file something holds open
* fix(studio-server): a restore waits for the preview's reads to close before replacing the file
* fix(studio-server): a restore checks the project folder after the preview's reads close
* feat(core): name why a choppy preview window dropped frames
The playback reading now says how much of the window was frames skipped
and how much was a playing video waiting for data, and carries the p50
and p95 gap between the runtime's ticks, so a report can tell a slow
compositor from a stalled decoder.
* chore(core): drop comments the ratchet counts
* fix(core): a graded clip's canvas no longer comes out blank on a real GPU
* chore(core): cut the comments the grading fix added
* fix(core): a clip that outgrows the GPU's buffer goes back to its source video
* 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
* 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
* 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
* 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
* 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
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.
* 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
* 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