* feat(cli): one owner for init media placeholders, audio stays on the video Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cli): init keeps a video's sound on the clip; silent files scaffold muted Supersedes the patchVideoSrc changes in #3977 and #3942 and carries their tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: miga-heygen <miga-heygen@users.noreply.github.com> Co-Authored-By: kapelame <kapelame@users.noreply.github.com> * feat(registry): example templates keep the a-roll's sound on the video Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(lint): video audio rules recommend keeping sound on the video Update video_missing_muted and video_audio_double_source message and fixHint text to recommend the new default: keep audio on the video with data-has-audio="true" (not muted). Tests verify the new wording is in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(parsers): a video is either audible (data-has-audio) or muted, never both Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): follow the compiler's audio rule in the audio-track toggle Confirm before background removal drops a video's sound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): hyperframes-core teaches sound on the video by default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): workflows and scaffold guidance keep a video's sound on the clip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: footage with sound keeps it on the video clip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): init from-file a-roll is timed, muted strip is attribute-exact The from-file a-roll video gets data-start="0" so a scaffold lints clean, and the orphaned audio-only slot comment is dropped. setVideoAudio now walks the tag attribute by attribute, so a class value containing the word muted survives. The ffprobe-missing warning says the video is assumed to have sound and how to switch to muted, and DEFAULT_META notes the failure surfaces at render until lint support lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep has-audio in step with the muted toggle, test cutout confirm Muting a video now clears has-audio and unmuting sets it to true, in both the Design and flat media panels, matching the Has audio track toggle. Audio elements are unchanged. Adds coverage for both panels plus the cutout window.confirm guard: declined leaves src alone, accepted swaps src and mutes, a silent video never prompts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(parsers): updateElementInHtml hasAudio only applies to videos, true or false An audio element or an undefined hasAudio no longer picks up a muted attribute; only an explicit false on a video writes muted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: video_missing_muted rule text, silent-footage notes, consistent audible examples lint.mdx and cli.mdx describe the current video_missing_muted rule instead of the retired unmuted-video wording. The editing recipes say how to adapt for silent footage and require the outgoing clip's sound to end at a J-cut. The Remotion media example shows the audible mapping consistently, data-attributes states that has-audio is required unless muted, and the hairline frame note no longer claims the video is muted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): encode init media filenames and probe --audio duration Function replacers stop dollar-sign pattern expansion in filenames; src is percent-encoded. --audio now passes the probed track length as the composition duration in both init paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(lint): audio-state-aware media rules and time-aware double-source check video_missing_muted honors any explicit data-has-audio. video_audio_double_source only considers audible videos and only fires when the same-src clips overlap in time. duplicate_audio_track now covers audible videos. audio_file_without_element no longer claims silence when an audible video carries the sound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): audio-stream check for remotion ports, followable J-cut recipe Remotion Video maps to data-has-audio only when the file has an audio stream. The J/L-cut recipe now shows both shots with muted pictures and separate audio. embedded-captions removes the audio.mp3 scratch file it extracts. skills-manifest.json regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(lint): trim comments to satisfy the comment ratchet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): atomic muted edits, cutout keeps sound on a linked audio Muted and Has audio track commit one batched write (one persist, one undo entry) and remove data-has-audio instead of writing an empty marker. Unmuting a video probes its src: audible -> true, silent -> false, probe failed -> absent. Remove background on an audible video inserts a linked sibling <audio> with the original sound in the same write, replacing the drop-the-sound confirm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(producer): skip hidden videos when auto-adding video audio A video hidden in Studio is dropped by the mixer, so its src never reached existingAudioSrcs and the render re-added it at full volume. The video parser now records the shared hidden predicate and the auto-detect step skips hidden videos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep useDomEditSession under the 600-line cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: miga-heygen <miga-heygen@users.noreply.github.com> Co-authored-by: kapelame <kapelame@users.noreply.github.com>
15 KiB
Remotion → HyperFrames API Map
Authoritative translation table. Load this reference when starting a translation to know the high-level mapping; load the per-topic references for fragile details (timing, transitions, etc.).
Reading this table
drop= remove from output entirely. The HF runtime handles it.see references/X.md= the mapping is non-trivial; read the linked file.refuse + interop= the skill bows out and recommends the runtime adapter pattern from PR #214.
Composition root
| Remotion | HyperFrames |
|---|---|
<Composition id durationInFrames fps width height> |
root <div id="stage" data-composition-id data-start="0" data-duration="<dur/fps>" data-fps data-width data-height> |
defaultProps={...} |
data-* attributes on #stage (one per scalar prop). Nested objects/arrays — see parameters.md |
schema={z.object(...)} |
not represented in HTML; the schema lives in the agent's translation step only |
calculateMetadata (sync) |
resolve at translation time, write concrete values into data-* |
calculateMetadata (async) |
refuse + interop — see escape-hatch.md |
registerRoot(RemotionRoot) |
drop |
<AbsoluteFill style> |
<div style="position:absolute;inset:0;{style}"> |
Sequencing
See sequencing.md for nesting and stagger details.
| Remotion | HyperFrames |
|---|---|
<Sequence from={F} durationInFrames={D}> |
a host <div data-composition-src="compositions/<name>.html" data-start="<F/fps>" data-duration="<D/fps>" data-track-index="N"> in the root, its content in that sub-composition file (see sequencing.md) |
<Series> + <Series.Sequence> |
sibling scene hosts with sequential data-start values |
<Loop durationInFrames={D}> |
not a primitive — emit a bounded GSAP repeat from the available duration |
<Freeze frame={F}> |
drop the wrapper; HF doesn't have running animation outside the seek-driven timeline so freeze is a no-op |
Timing
See timing.md — this is the highest-leverage section.
| Remotion | HyperFrames |
|---|---|
useCurrentFrame() |
drop — HF seeks the timeline. The math derived from frame becomes an animatable property of a paused GSAP tween. |
useVideoConfig() for fps / durationInFrames |
drop — read from data-fps / data-duration on #stage |
interpolate(frame, [a,b], [x,y]) (linear) |
gsap.fromTo(t, {p:x}, {p:y, duration:(b-a)/fps, ease:"none"}) at offset a/fps |
interpolate(frame, [a,b,c,d], [x,y,y,z]) (multi-segment) |
three gsap.to calls at offsets a/fps, b/fps, c/fps |
interpolate(..., {easing: Easing.bezier}) |
GSAP CustomEase.create("c", "M0,0 C${a},${b} ${c},${d} 1,1") |
spring({frame, fps, config: {damping, stiffness, mass}}) |
GSAP back.out(N) — see timing.md for damping → overshoot table |
interpolateColors(frame, range, colors) |
gsap.to({...}, { backgroundColor, color, duration, ease }) — GSAP handles color tweens natively |
Easing.in / .out / .inOut(power) |
GSAP power<N>.in / power<N>.out / power<N>.inOut |
Media
See media.md for trim, volume ramps, and decoder notes.
| Remotion | HyperFrames |
|---|---|
<Audio src volume> |
<audio data-start data-duration data-track-index data-volume src> |
<Audio playbackRate startFrom endAt> |
data-playback-rate, data-trim-start, data-trim-end |
<Video src> |
<video playsinline data-has-audio="true" data-start data-duration data-track-index src> only when the file has an audio stream (check with ffprobe -v error -select_streams a -show_entries stream=codec_type -of csv=p=0 <file>: non-empty means it has audio) and the source is not muted or volume={0}; otherwise muted instead |
<OffthreadVideo> |
<video> — HF doesn't need the off-thread variant (uses headless Chrome) |
<Img src> |
<img> |
<IFrame src> |
<iframe> — HF auto-falls back to screenshot mode for nested iframes |
staticFile("x.png") |
"assets/x.png" in the root, "../assets/x.png" inside a compositions/ scene file — copy the file into hf-src/assets/ next to index.html |
delayRender() / continueRender() |
drop — HF waits on asset readiness via the Frame Adapter pattern |
Transitions
See transitions.md.
| Remotion | HyperFrames |
|---|---|
<TransitionSeries> + <TransitionSeries.Transition presentation={fade()} /> |
manual gsap.to(scene, {opacity: 0/1, duration}) crossfade at the boundary |
slide(), wipe(), clockWipe(), fade() |
HF shader-transitions package presets — pick the closest |
linearTiming({durationInFrames}) |
duration in seconds (/fps) |
springTiming({config}) |
duration in seconds, ease back.out — see timing.md |
Lottie
See lottie.md.
| Remotion | HyperFrames |
|---|---|
<Lottie animationData={data}> |
<div id="lottie-N"> + <script>const anim = lottie.loadAnimation({...}); window.__hfLottie.push(anim)</script> |
loop / playbackRate props |
translate only after checking player seek behavior; HF adapter seeks absolute time via goToAndStop |
@remotion/lottie runtime |
lottie-web from CDN — drop the React wrapper |
Fonts
See fonts.md.
| Remotion | HyperFrames |
|---|---|
loadFont() from @remotion/google-fonts/<Family> |
@font-face rule referencing the Google Fonts CSS, OR <link> to Google Fonts in <head> |
Local font via @font-face |
same — paste the rule into <style> |
| System font fallback | document the font-fallback divergence cost (see eval.md) |
Parameters
See parameters.md.
| Remotion | HyperFrames |
|---|---|
z.object({foo: z.string()}) |
data-foo on #stage (the schema is implicit in HTML structure) |
nested array prop (stats[]) |
repeated HTML markup with per-instance data-* attrs |
| Zod default values | bake defaults into the HTML directly |
| Zod runtime validation | not represented; if validation matters, validate in the translation step before emitting HTML |
React patterns
| Remotion | HyperFrames |
|---|---|
| Custom React subcomponent (pure, prop-driven) | inline as repeated HTML using the prop interface as the template |
useState driving animation |
refuse + interop |
useReducer driving animation |
refuse + interop |
useEffect(fn, [deps]) (non-empty deps) |
refuse + interop |
useEffect(fn, []) (mount-once side effect) |
drop the effect; use queueMicrotask if startup work is needed |
useCallback, useMemo |
drop the wrappers — decorative |
Custom hook (pure derivation of useCurrentFrame) |
inline the body |
| Custom hook with state/effects | refuse + interop |
Distributed rendering
@remotion/lambda and @remotion/cloudrun are deployment configuration —
orthogonal to the rendered composition itself. The skill emits these as
warnings (not blockers) and drops them in step 3 (Generate) with a note
in TRANSLATION_NOTES.md. HF is single-machine today; document the gap.
| Remotion | HyperFrames |
|---|---|
@remotion/lambda import |
drop the import (warning r2hf/lambda-import) |
renderMediaOnLambda(...) |
drop the call; note in TRANSLATION_NOTES.md |
@remotion/cloudrun |
drop the import + call; note in TRANSLATION_NOTES.md |
When to bow out entirely
If any blocker pattern is present, recommend the runtime interop pattern from PR #214 instead of attempting translation. See escape-hatch.md.
The blockers are documented in scripts/lint_source.py
and tested by tier-4-escape-hatch.